Function bodies 345 total
generateMetadata function · typescript · L48-L89 (42 LOC)src/app/t/[tenant]/blog/[slug]/page.tsx
export async function generateMetadata({ params }: TenantPostPageProps): Promise<Metadata> {
const { tenant: tenantSlug, slug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) {
return { title: { absolute: 'Not Found' } };
}
const tenantId = tenant._id.toString();
const [post, review, settings] = await Promise.all([
getPostBySlug(slug, tenantId),
getReviewBySlugOrId(slug, tenantId),
(async () => {
await connectDB();
return Settings.findOne({ tenantId: tenant._id }).lean();
})(),
]);
// Use post or review data
const content = post || review;
if (!content) {
return { title: { absolute: 'Post Not Found' } };
}
const siteName = settings?.siteName || tenant.settings.siteName || tenant.name;
const title = post ? post.title : review?.generatedReview?.title || review?.product?.name;
const description = post ? post.description : review?.generatedReview?.excerpt || `Review of ${review?.product?.namgetSettings function · typescript · L91-L95 (5 LOC)src/app/t/[tenant]/blog/[slug]/page.tsx
async function getSettings(tenantId: string) {
await connectDB();
const settings = await Settings.findOne({ tenantId }).lean();
return settings;
}generateMetadata function · typescript · L12-L40 (29 LOC)src/app/t/[tenant]/layout.tsx
export async function generateMetadata({ params }: TenantLayoutProps): Promise<Metadata> {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) {
return { title: { absolute: 'Not Found' } };
}
// Fetch settings from Settings model (where user customizes their site)
await connectDB();
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings.siteName || tenant.name;
const siteDescription = settings?.siteDescription || tenant.settings.siteDescription || `Welcome to ${tenant.name}`;
const favicon = settings?.favicon || '';
return {
title: {
absolute: siteName,
template: `%s | ${siteName}`,
},
description: siteDescription,
icons: favicon ? {
icon: [{ url: favicon }],
shortcut: [{ url: favicon }],
apple: [{ url: favicon }],
} : undefined,
};
}TenantLayout function · typescript · L42-L55 (14 LOC)src/app/t/[tenant]/layout.tsx
export default async function TenantLayout({ children, params }: TenantLayoutProps) {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) {
notFound();
}
return (
<div data-tenant={tenantSlug}>
{children}
</div>
);
}TenantNotFound function · typescript · L6-L43 (38 LOC)src/app/t/[tenant]/not-found.tsx
export default function TenantNotFound() {
const params = useParams();
const tenant = params?.tenant as string;
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center px-4">
<div className="text-center">
<h1 className="text-6xl font-bold text-gray-900 dark:text-white mb-4">404</h1>
<h2 className="text-2xl font-semibold text-gray-700 dark:text-gray-300 mb-4">
Page Not Found
</h2>
<p className="text-gray-600 dark:text-gray-400 mb-8 max-w-md">
Sorry, the page you are looking for doesn't exist or has been moved.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4">
<Link
href={tenant ? `/t/${tenant}` : '/'}
className="inline-flex items-center gap-2 px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-500 transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="generateMetadata function · typescript · L21-L43 (23 LOC)src/app/t/[tenant]/[pageSlug]/page.tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { tenant: tenantSlug, pageSlug } = await params;
// Skip reserved slugs
if (RESERVED_SLUGS.includes(pageSlug.toLowerCase())) {
return {};
}
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) return {};
await connectDB();
const page = await Page.findOne({ tenantId: tenant._id, slug: pageSlug, published: true }).lean() as any;
if (!page) return {};
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.name;
return {
title: page.seoTitle || `${page.title} | ${siteName}`,
description: page.seoDescription || page.heroSubtitle || `${page.title} - ${siteName}`,
};
}DynamicPage function · typescript · L45-L441 (397 LOC)src/app/t/[tenant]/[pageSlug]/page.tsx
export default async function DynamicPage({ params }: PageProps) {
const { tenant: tenantSlug, pageSlug } = await params;
// Skip reserved slugs - let Next.js handle 404
if (RESERVED_SLUGS.includes(pageSlug.toLowerCase())) {
notFound();
}
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) {
notFound();
}
await connectDB();
const page = await Page.findOne({
tenantId: tenant._id,
slug: pageSlug,
published: true,
}).lean() as any;
if (!page) {
notFound();
}
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings?.siteName || tenant.name;
const siteLogo = settings?.siteLogo || '';
const primaryColor = settings?.primaryColor || '#6366f1';
const accentColor = settings?.accentColor || '#8b5cf6';
// Get pages for navbar
const navPages = await Page.find({
tenantId: tenant._id,
published: true,
showInNavbar: true,
}).sort({ navbarHi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
generateMetadata function · typescript · L21-L50 (30 LOC)src/app/t/[tenant]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ tenant: string }> }): Promise<Metadata> {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) return {};
await connectDB();
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings.siteName || tenant.name;
const seoDescription = settings?.seoDescription || settings?.siteDescription || 'Welcome to our blog';
const seoKeywords = settings?.seoKeywords || [];
const ogImage = settings?.ogImage || '';
return {
description: seoDescription,
keywords: seoKeywords.join(', '),
openGraph: {
title: siteName,
description: seoDescription,
images: ogImage ? [ogImage] : [],
siteName: siteName,
},
twitter: {
card: 'summary_large_image',
title: siteName,
description: seoDescription,
images: ogImage ? [ogImage] : [],
generateMetadata function · typescript · L16-L29 (14 LOC)src/app/t/[tenant]/privacy/page.tsx
export async function generateMetadata({ params }: PrivacyPageProps): Promise<Metadata> {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) return {};
await connectDB();
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings?.siteName || tenant.name;
return {
title: `Privacy Policy | ${siteName}`,
description: `Privacy Policy for ${siteName}`,
};
}PrivacyPage function · typescript · L31-L151 (121 LOC)src/app/t/[tenant]/privacy/page.tsx
export default async function PrivacyPage({ params }: PrivacyPageProps) {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) {
notFound();
}
await connectDB();
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings?.siteName || tenant.name;
const siteLogo = settings?.siteLogo || '';
const primaryColor = settings?.primaryColor || '#6366f1';
const accentColor = settings?.accentColor || '#8b5cf6';
const privacyPolicy = settings?.privacyPolicy || '';
// Footer settings
const copyrightText = (settings?.copyrightText || '© {year} All rights reserved.')
.replace('{year}', new Date().getFullYear().toString());
const socialLinks = (settings?.socialLinks || {}) as any;
const contactEmail = settings?.contactEmail || '';
const contactPhone = settings?.contactPhone || '';
const contactAddress = settings?.contactAddress || ''getReviewData function · typescript · L16-L51 (36 LOC)src/app/t/[tenant]/reviews/[slug]/page.tsx
async function getReviewData(tenantSlug: string, reviewSlugOrId: string) {
await connectDB();
const tenant = await Tenant.findOne({
slug: { $regex: new RegExp(`^${tenantSlug}$`, 'i') },
}).lean();
if (!tenant) {
return null;
}
// First try to find by slug
let review = await Review.findOne({
tenantId: tenant._id,
'generatedReview.slug': reviewSlugOrId,
published: true,
}).lean();
// If not found and it looks like a MongoDB ObjectId, try to find by _id
if (!review && /^[a-f0-9]{24}$/i.test(reviewSlugOrId)) {
review = await Review.findOne({
_id: reviewSlugOrId,
tenantId: tenant._id,
published: true,
}).lean();
}
if (!review) {
return null;
}
return {
tenant: JSON.parse(JSON.stringify(tenant)),
review: JSON.parse(JSON.stringify(review)),
};
}generateMetadata function · typescript · L53-L75 (23 LOC)src/app/t/[tenant]/reviews/[slug]/page.tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { tenant: tenantSlug, slug } = await params;
const data = await getReviewData(tenantSlug, slug);
if (!data) {
return { title: 'Review Not Found' };
}
const { tenant, review } = data;
const title = review.seoTitle || review.generatedReview?.title || 'Review';
const description =
review.seoDescription || review.generatedReview?.excerpt || `Read the full review on ${tenant.name}`;
return {
title: `${title} | ${tenant.name}`,
description,
openGraph: {
title,
description,
images: review.featuredImage ? [review.featuredImage] : [],
},
};
}ReviewPage function · typescript · L77-L339 (263 LOC)src/app/t/[tenant]/reviews/[slug]/page.tsx
export default async function ReviewPage({ params }: PageProps) {
const { tenant: tenantSlug, slug } = await params;
const data = await getReviewData(tenantSlug, slug);
if (!data) {
notFound();
}
const { tenant, review } = data;
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
{/* Navigation */}
<nav className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-50">
<div className="max-w-4xl mx-auto px-4 py-4">
<div className="flex items-center justify-between">
<Link
href={`/t/${tenantSlug}`}
className="text-xl font-bold text-gray-900 dark:text-white"
>
{tenant.name}
</Link>
<Link
href={`/t/${tenantSlug}/blog`}
className="text-sm text-gray-600 dark:text-gray-400 hover:text-indigo-600"
>
← Back to Blog
</Link>
<generateMetadata function · typescript · L16-L29 (14 LOC)src/app/t/[tenant]/terms/page.tsx
export async function generateMetadata({ params }: TermsPageProps): Promise<Metadata> {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) return {};
await connectDB();
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings?.siteName || tenant.name;
return {
title: `Terms of Service | ${siteName}`,
description: `Terms of Service for ${siteName}`,
};
}TermsPage function · typescript · L31-L151 (121 LOC)src/app/t/[tenant]/terms/page.tsx
export default async function TermsPage({ params }: TermsPageProps) {
const { tenant: tenantSlug } = await params;
const tenant = await getTenantBySlug(tenantSlug);
if (!tenant) {
notFound();
}
await connectDB();
const settings = await Settings.findOne({ tenantId: tenant._id }).lean();
const siteName = settings?.siteName || tenant.settings?.siteName || tenant.name;
const siteLogo = settings?.siteLogo || '';
const primaryColor = settings?.primaryColor || '#6366f1';
const accentColor = settings?.accentColor || '#8b5cf6';
const termsOfService = settings?.termsOfService || '';
// Footer settings
const copyrightText = (settings?.copyrightText || '© {year} All rights reserved.')
.replace('{year}', new Date().getFullYear().toString());
const socialLinks = (settings?.socialLinks || {}) as any;
const contactEmail = settings?.contactEmail || '';
const contactPhone = settings?.contactPhone || '';
const contactAddress = settings?.contactAddress || '';
All rows above produced by Repobility · https://repobility.com
DeleteAuthorButton function · typescript · L11-L51 (41 LOC)src/components/admin/DeleteAuthorButton.tsx
export function DeleteAuthorButton({ authorId }: DeleteAuthorButtonProps) {
const [loading, setLoading] = useState(false);
const router = useRouter();
const handleDelete = async () => {
if (!confirm('Are you sure you want to delete this author? This will not delete their posts.')) {
return;
}
setLoading(true);
try {
const response = await fetch(`/api/admin/authors/${authorId}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete author');
}
router.refresh();
} catch (error) {
alert('Failed to delete author');
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleDelete}
disabled={loading}
className="rounded-lg p-2 text-red-500 hover:bg-red-50 disabled:opacity-50 dark:hover:bg-red-900/20"
title="Delete author"
aria-label="Delete author"
>
<Trash2 className="h-4 w-4" aria-hidden="true" />
<spaDeletePostButton function · typescript · L11-L49 (39 LOC)src/components/admin/DeletePostButton.tsx
export function DeletePostButton({ postId }: DeletePostButtonProps) {
const [loading, setLoading] = useState(false);
const router = useRouter();
const handleDelete = async () => {
if (!confirm('Are you sure you want to delete this post?')) {
return;
}
setLoading(true);
try {
const response = await fetch(`/api/admin/posts/${postId}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete post');
}
router.refresh();
} catch (error) {
alert('Failed to delete post');
} finally {
setLoading(false);
}
};
return (
<button
onClick={handleDelete}
disabled={loading}
className="rounded-lg p-2 text-red-500 hover:bg-red-50 disabled:opacity-50 dark:hover:bg-red-900/20"
title="Delete"
>
<Trash2 className="h-4 w-4" />
</button>
);
}FeaturePostButton function · typescript · L12-L51 (40 LOC)src/components/admin/FeaturePostButton.tsx
export function FeaturePostButton({ postId, isFeatured }: FeaturePostButtonProps) {
const [featured, setFeatured] = useState(isFeatured);
const [loading, setLoading] = useState(false);
const router = useRouter();
const toggleFeatured = async () => {
setLoading(true);
try {
const res = await fetch(`/api/admin/posts/${postId}/feature`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ featured: !featured }),
});
if (res.ok) {
setFeatured(!featured);
router.refresh();
}
} catch (error) {
console.error('Failed to toggle featured:', error);
} finally {
setLoading(false);
}
};
return (
<button
onClick={toggleFeatured}
disabled={loading}
className={`rounded-lg p-2 transition-colors ${
featured
? 'text-yellow-500 hover:bg-yellow-50 dark:hover:bg-yellow-900/20'
: 'text-gray-400 hover:bg-gray-100 fetchCategories function · typescript · L62-L82 (21 LOC)src/components/admin/PostForm.tsx
async function fetchCategories() {
try {
const res = await fetch('/api/admin/categories');
if (res.ok) {
const data = await res.json();
// Admin API returns array directly, filter out orphaned categories
const validCategories = Array.isArray(data)
? data.filter((cat: any) => !cat.isOrphaned)
: [];
setCategories(validCategories);
// Set default category for new posts
if (!initialData?.category && validCategories.length > 0) {
setFormData(prev => ({ ...prev, category: validCategories[0].slug }));
}
}
} catch (err) {
console.error('Failed to fetch categories:', err);
} finally {
setLoadingCategories(false);
}
}getValidImageSrc function · typescript · L5-L11 (7 LOC)src/components/author/AuthorBio.tsx
function getValidImageSrc(image: string | undefined | null): string {
if (!image) return DEFAULT_AVATAR;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return DEFAULT_AVATAR;
}AuthorBio function · typescript · L29-L109 (81 LOC)src/components/author/AuthorBio.tsx
export function AuthorBio({ author, postCount }: AuthorBioProps) {
return (
<div className="mb-12 flex flex-col items-center text-center md:flex-row md:items-start md:text-left">
<Image
src={getValidImageSrc(author.avatar)}
alt={author.name}
width={120}
height={120}
className="mb-6 rounded-full md:mb-0 md:mr-8"
/>
<div>
<h1 className="mb-2 text-3xl font-bold text-gray-900 dark:text-white">{author.name}</h1>
<p className="mb-3 text-lg text-gray-600 dark:text-gray-400">{author.role}</p>
<p className="mb-4 max-w-2xl text-gray-700 dark:text-gray-300">{author.bio}</p>
{/* Stats */}
<div className="mb-4">
<span className="text-sm text-gray-500 dark:text-gray-400">
{postCount} {postCount === 1 ? 'post' : 'posts'} published
</span>
</div>
{/* Social Links */}
<div className="flex justify-center gap-4 md:justify-start">
{authgetValidImageSrc function · typescript · L6-L12 (7 LOC)src/components/author/AuthorCard.tsx
function getValidImageSrc(image: string | undefined | null): string {
if (!image) return DEFAULT_AVATAR;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return DEFAULT_AVATAR;
}AuthorCard function · typescript · L24-L44 (21 LOC)src/components/author/AuthorCard.tsx
export function AuthorCard({ author }: AuthorCardProps) {
return (
<Link
href={`/authors/${author.slug}`}
className="group flex flex-col items-center rounded-xl border border-gray-200 bg-white p-6 text-center shadow-sm transition-shadow hover:shadow-md dark:border-gray-800 dark:bg-gray-900"
>
<Image
src={getValidImageSrc(author.avatar)}
alt={author.name}
width={80}
height={80}
className="mb-4 rounded-full"
/>
<h3 className="mb-1 text-lg font-semibold text-gray-900 transition-colors group-hover:text-primary-600 dark:text-white dark:group-hover:text-primary-400">
{author.name}
</h3>
<p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{author.role}</p>
<p className="line-clamp-2 text-sm text-gray-500 dark:text-gray-500">{author.bio}</p>
</Link>
);
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
getValidImageSrc function · typescript · L55-L61 (7 LOC)src/components/blog/BlogFilters.tsx
function getValidImageSrc(image: string | undefined | null): string {
if (!image) return DEFAULT_IMAGE;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return DEFAULT_IMAGE;
}getCategoryName function · typescript · L63-L65 (3 LOC)src/components/blog/BlogFilters.tsx
function getCategoryName(slug: string, categories: { slug: string; name: string }[]): string {
return categories.find(c => c.slug === slug)?.name || slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, ' ');
}BreakingNewsTicker function · typescript · L17-L81 (65 LOC)src/components/blog/BreakingNewsTicker.tsx
export default function BreakingNewsTicker({
news,
basePath,
label = 'Latest',
speed = 30
}: BreakingNewsTickerProps) {
if (!news.length) return null;
// Duplicate news for seamless infinite scroll
const duplicatedNews = [...news, ...news];
return (
<div className="fixed top-0 left-0 right-0 z-[60] bg-gradient-to-r from-red-600 to-red-500 text-white overflow-hidden">
<div className="container mx-auto px-4">
<div className="flex items-center h-10">
{/* Label */}
<div className="flex items-center gap-2 pr-4 border-r border-white/30 flex-shrink-0 z-10 bg-gradient-to-r from-red-600 to-red-500">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-white opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-white"></span>
</span>
<span className="text-xs font-bold uppercase tracgetValidImageSrc function · typescript · L8-L14 (7 LOC)src/components/blog/FeaturedPost.tsx
function getValidImageSrc(image: string | undefined | null): string {
if (!image) return DEFAULT_IMAGE;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return DEFAULT_IMAGE;
}FeaturedPost function · typescript · L33-L107 (75 LOC)src/components/blog/FeaturedPost.tsx
export function FeaturedPost({ post }: FeaturedPostProps) {
const imageSrc = getValidImageSrc(post.image);
return (
<article className="group">
<Link href={`/blog/${post.slug}`} className="block">
{/* Image Container - Fixed aspect ratio */}
<div className="relative mb-6 overflow-hidden rounded-2xl bg-gray-100 dark:bg-gray-800">
<div className="aspect-[2/1]">
<Image
src={imageSrc}
alt={post.title}
fill
priority
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
className="object-cover"
/>
</div>
{/* Category Badge */}
<div className="absolute left-4 top-4">
<CategoryBadge category={post.category} />
</div>
</div>
</Link>
{/* Content */}
<div className="space-y-4">
{/* Meta */}
<div className="flex items-center gap-4 text-sm text-gChevronIcon function · typescript · L23-L34 (12 LOC)src/components/blog/FilterSidebar.tsx
function ChevronIcon({ expanded }: { expanded: boolean }) {
return (
<svg
className={`w-4 h-4 transition-transform duration-200 ${expanded ? 'rotate-180' : ''}`}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
);
}SearchIcon function · typescript · L36-L42 (7 LOC)src/components/blog/FilterSidebar.tsx
function SearchIcon() {
return (
<svg className="w-3.5 h-3.5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
);
}ClearIcon function · typescript · L44-L50 (7 LOC)src/components/blog/FilterSidebar.tsx
function ClearIcon() {
return (
<svg className="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
);
}Repobility — same analyzer, your code, free for public repos · /scan/
FilterSidebar function · typescript · L52-L243 (192 LOC)src/components/blog/FilterSidebar.tsx
export function FilterSidebar({
basePath,
categories,
tags,
totalPosts,
activeCategory,
activeTag,
activeSearch,
}: FilterSidebarProps) {
const [tagsExpanded, setTagsExpanded] = useState(false);
const [tagSearch, setTagSearch] = useState('');
const COLLAPSED_TAGS_COUNT = 8;
const EXPANDED_TAGS_COUNT = 30;
// Filter tags based on search
const filteredTags = useMemo(() => {
if (!tagSearch.trim()) return tags;
const search = tagSearch.toLowerCase();
return tags.filter(tag => tag.toLowerCase().includes(search));
}, [tags, tagSearch]);
// Determine visible tags
const visibleTags = useMemo(() => {
const limit = tagSearch.trim() ? EXPANDED_TAGS_COUNT : (tagsExpanded ? EXPANDED_TAGS_COUNT : COLLAPSED_TAGS_COUNT);
return filteredTags.slice(0, limit);
}, [filteredTags, tagsExpanded, tagSearch]);
const hasMoreTags = filteredTags.length > visibleTags.length;
// Build filter URL helper
const buildFilterUrl = (params: { category?: striPostCard function · typescript · L24-L80 (57 LOC)src/components/blog/PostCard.tsx
export function PostCard({ post }: PostCardProps) {
const imageSrc = getValidPostImage(post.image);
return (
<article className="group">
{/* Image */}
<Link href={`/blog/${post.slug}`} className="block">
<div className="relative mb-4 overflow-hidden rounded-xl bg-gray-100 dark:bg-gray-800">
<div className="aspect-[16/10]">
<Image
src={imageSrc}
alt={post.title}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 400px"
className="object-cover"
/>
</div>
</div>
</Link>
{/* Content */}
<div className="space-y-2">
{/* Category & Meta */}
<div className="flex items-center gap-3 text-sm">
<CategoryBadge category={post.category} href={`/blog?category=${post.category}`} />
<span className="text-gray-500 dark:text-gray-400">{post.readingTime}</span>
</div>
{/* TigetValidImageSrc function · typescript · L11-L17 (7 LOC)src/components/blog/PostHeader.tsx
function getValidImageSrc(image: string | undefined | null, defaultSrc: string): string {
if (!image) return defaultSrc;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return defaultSrc;
}PostHeader function · typescript · L37-L108 (72 LOC)src/components/blog/PostHeader.tsx
export function PostHeader({ post, author }: PostHeaderProps) {
return (
<header className="mb-8">
{/* Category */}
<div className="mb-4">
<CategoryBadge category={post.category} href={`/blog?category=${post.category}`} />
</div>
{/* Title */}
<h1 className="mb-4 text-3xl font-bold text-gray-900 dark:text-white md:text-4xl lg:text-5xl">
{post.title}
</h1>
{/* Description */}
<p className="mb-6 text-lg text-gray-600 dark:text-gray-400 md:text-xl">
{post.description}
</p>
{/* Meta */}
<div className="mb-6 flex flex-wrap items-center gap-4">
{/* Author */}
{author && (
<Link href={`/authors/${author.slug}`} className="flex items-center gap-3">
<Image
src={getValidImageSrc(author.avatar, DEFAULT_AVATAR)}
alt={author.name}
width={40}
height={40}
className="rounded-full"
/>
PostList function · typescript · L9-L29 (21 LOC)src/components/blog/PostList.tsx
export function PostList({ posts, columns = 3 }: PostListProps) {
if (posts.length === 0) {
return (
<div className="py-12 text-center">
<p className="text-gray-500 dark:text-gray-400">No posts found.</p>
</div>
);
}
const gridCols = columns === 2
? 'md:grid-cols-2'
: 'md:grid-cols-2 lg:grid-cols-3';
return (
<div className={`grid gap-6 ${gridCols}`}>
{posts.map((post) => (
<PostCard key={post.slug} post={post} />
))}
</div>
);
}RelatedPosts function · typescript · L8-L23 (16 LOC)src/components/blog/RelatedPosts.tsx
export function RelatedPosts({ posts }: RelatedPostsProps) {
if (posts.length === 0) return null;
return (
<section className="mt-16 border-t border-gray-200 pt-12 dark:border-gray-800">
<h2 className="mb-8 text-2xl font-bold text-gray-900 dark:text-white">
Related Posts
</h2>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{posts.slice(0, 3).map((post) => (
<PostCard key={post.slug} post={post} />
))}
</div>
</section>
);
}ShareButtons function · typescript · L10-L104 (95 LOC)src/components/blog/ShareButtons.tsx
export function ShareButtons({ title, url }: ShareButtonsProps) {
const encodedUrl = encodeURIComponent(url);
const encodedTitle = encodeURIComponent(title);
const shareLinks = [
{
name: 'Twitter',
href: `https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`,
icon: (
<svg className="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84" />
</svg>
),
},
{
name: 'LinkedIn',
href: `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`,
icon: (
getValidImageSrc function · typescript · L55-L61 (7 LOC)src/components/blog/Sidebar.tsx
function getValidImageSrc(image: string | undefined | null): string {
if (!image) return DEFAULT_IMAGE;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return DEFAULT_IMAGE;
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
AboutWidget function · typescript · L64-L82 (19 LOC)src/components/blog/Sidebar.tsx
function AboutWidget({ title, config }: { title?: string; config?: SidebarWidget['config'] }) {
return (
<div className="rounded-2xl bg-white dark:bg-gray-900 p-6 shadow-sm border border-gray-100 dark:border-gray-800">
{title && (
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{title}</h3>
)}
{config?.imageUrl && (
<div className="relative w-20 h-20 mx-auto mb-4 rounded-full overflow-hidden">
<Image src={config.imageUrl} alt="About" fill className="object-cover" />
</div>
)}
{config?.content && (
<p className="text-gray-600 dark:text-gray-400 text-sm leading-relaxed">
{config.content}
</p>
)}
</div>
);
}RecentPostsWidget function · typescript · L84-L126 (43 LOC)src/components/blog/Sidebar.tsx
function RecentPostsWidget({
title,
posts,
basePath
}: {
title?: string;
posts: Post[];
basePath: string;
}) {
if (!posts || posts.length === 0) return null;
return (
<div className="rounded-2xl bg-white dark:bg-gray-900 p-6 shadow-sm border border-gray-100 dark:border-gray-800">
{title && (
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{title}</h3>
)}
<div className="space-y-4">
{posts.map((post) => (
<Link
key={post._id}
href={`${basePath}/blog/${post.slug}`}
className="flex gap-3 group"
>
<div className="relative w-16 h-16 flex-shrink-0 rounded-lg overflow-hidden">
<Image
src={getValidImageSrc(post.image)}
alt={post.title}
fill
className="object-cover group-hover:scale-105 transition-transform duration-300"
/>
</div>
<div claCategoriesWidget function · typescript · L128-L166 (39 LOC)src/components/blog/Sidebar.tsx
function CategoriesWidget({
title,
categories,
showCount,
basePath
}: {
title?: string;
categories: Category[];
showCount?: boolean;
basePath: string;
}) {
if (!categories || categories.length === 0) return null;
return (
<div className="rounded-2xl bg-white dark:bg-gray-900 p-6 shadow-sm border border-gray-100 dark:border-gray-800">
{title && (
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{title}</h3>
)}
<div className="space-y-2">
{categories.map((category) => (
<Link
key={category.slug}
href={`${basePath}/blog?category=${category.slug}`}
className="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors group"
>
<span className="text-sm text-gray-700 dark:text-gray-300 group-hover:text-indigo-600 dark:group-hover:text-indigo-400">
{category.name}
</spanTagsWidget function · typescript · L168-L197 (30 LOC)src/components/blog/Sidebar.tsx
function TagsWidget({
title,
tags,
basePath
}: {
title?: string;
tags: string[];
basePath: string;
}) {
if (!tags || tags.length === 0) return null;
return (
<div className="rounded-2xl bg-white dark:bg-gray-900 p-6 shadow-sm border border-gray-100 dark:border-gray-800">
{title && (
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{title}</h3>
)}
<div className="flex flex-wrap gap-2">
{tags.map((tag) => (
<Link
key={tag}
href={`${basePath}/blog?tag=${tag}`}
className="px-3 py-1.5 text-sm rounded-full bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-indigo-100 dark:hover:bg-indigo-900/30 hover:text-indigo-600 dark:hover:text-indigo-400 transition-colors"
>
{tag}
</Link>
))}
</div>
</div>
);
}NewsletterWidget function · typescript · L199-L223 (25 LOC)src/components/blog/Sidebar.tsx
function NewsletterWidget({ title }: { title?: string }) {
return (
<div className="rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600 p-6 shadow-sm">
{title && (
<h3 className="text-lg font-bold text-white mb-2">{title}</h3>
)}
<p className="text-indigo-100 text-sm mb-4">
Subscribe to get the latest posts delivered to your inbox.
</p>
<form className="space-y-3">
<input
type="email"
placeholder="Enter your email"
className="w-full px-4 py-2.5 rounded-lg bg-white/20 border border-white/30 text-white placeholder-white/60 focus:outline-none focus:ring-2 focus:ring-white/50"
/>
<button
type="submit"
className="w-full px-4 py-2.5 rounded-lg bg-white text-indigo-600 font-medium hover:bg-indigo-50 transition-colors"
>
Subscribe
</button>
</form>
</div>
);
}SocialLinksWidget function · typescript · L225-L267 (43 LOC)src/components/blog/Sidebar.tsx
function SocialLinksWidget({
title,
socialLinks
}: {
title?: string;
socialLinks: SocialLinks;
}) {
const links = [
{ key: 'twitter', icon: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z', label: 'Twitter' },
{ key: 'github', icon: 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.19CustomHtmlWidget function · typescript · L269-L283 (15 LOC)src/components/blog/Sidebar.tsx
function CustomHtmlWidget({ title, config }: { title?: string; config?: SidebarWidget['config'] }) {
if (!config?.content) return null;
return (
<div className="rounded-2xl bg-white dark:bg-gray-900 p-6 shadow-sm border border-gray-100 dark:border-gray-800">
{title && (
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-4">{title}</h3>
)}
<div
className="prose prose-sm dark:prose-invert max-w-none"
dangerouslySetInnerHTML={{ __html: config.content }}
/>
</div>
);
}Sidebar function · typescript · L285-L377 (93 LOC)src/components/blog/Sidebar.tsx
export function Sidebar({
widgets,
basePath,
recentPosts = [],
featuredPosts = [],
categories = [],
tags = [],
socialLinks = {},
siteName,
}: SidebarProps) {
const enabledWidgets = widgets.filter(w => w.enabled);
if (enabledWidgets.length === 0) return null;
return (
<aside className="space-y-6">
{enabledWidgets.map((widget) => {
switch (widget.type) {
case 'about':
return (
<AboutWidget
key={widget.id}
title={widget.title}
config={widget.config}
/>
);
case 'recentPosts':
return (
<RecentPostsWidget
key={widget.id}
title={widget.title}
posts={recentPosts.slice(0, widget.config?.count || 5)}
basePath={basePath}
/>
);
case 'featuredPosts':
return (
<RecentPostsWidget
All rows above produced by Repobility · https://repobility.com
Footer function · typescript · L15-L152 (138 LOC)src/components/layout/Footer.tsx
export function Footer() {
const currentYear = new Date().getFullYear();
return (
<footer className="border-t border-gray-200 bg-gray-50 dark:border-white/10 dark:bg-gray-950">
<div className="container mx-auto px-4 py-12">
{/* Main Footer Content */}
<div className="grid grid-cols-1 gap-8 md:grid-cols-4 lg:grid-cols-5">
{/* Brand Section */}
<div className="md:col-span-2">
<Link href="/" className="inline-flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-indigo-500 to-purple-600">
<svg className="h-5 w-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<span className="text-xl font-bold text-gray-900 dark:text-white">
{sHeader function · typescript · L13-L190 (178 LOC)src/components/layout/Header.tsx
export function Header() {
const { data: session, status } = useSession();
const router = useRouter();
const pathname = usePathname();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false);
// Close mobile menu on route change
useEffect(() => {
setIsMobileMenuOpen(false);
}, [pathname]);
return (
<header className="sticky top-0 z-50 w-full border-b border-gray-200 bg-white/80 backdrop-blur-xl dark:border-white/10 dark:bg-gray-950/80">
<div className="container mx-auto flex h-16 items-center justify-between px-4">
{/* Logo */}
<Link href="/" className="flex items-center gap-2">
<span className="text-xl font-bold text-gray-900 dark:text-white">
{siteConfig.name}
</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden items-center gap-1 md:flex">
{navItems.map((item) => {
const isActiLayoutWrapper function · typescript · L25-L53 (29 LOC)src/components/layout/LayoutWrapper.tsx
export function LayoutWrapper({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Routes that have their own layout (no Header/Footer from this wrapper)
const isAdminRoute = pathname?.startsWith('/admin');
const isSuperAdminRoute = pathname?.startsWith('/super-admin');
const isTenantRoute = pathname?.startsWith('/t/');
const isCustomDomainRoute = pathname?.startsWith('/cd');
const isAuthRoute = pathname === '/login' || pathname === '/signup';
// Check if it's a standalone route (these have their own inline navbar/footer)
const isStandaloneRoute = standaloneRoutes.some(route =>
pathname === route || pathname?.startsWith(route + '/')
);
// These routes handle their own layout
if (isAdminRoute || isSuperAdminRoute || isTenantRoute || isCustomDomainRoute || isAuthRoute || isStandaloneRoute) {
return <>{children}</>;
}
// Other routes (blog, authors, about, contact, search, etc.) use blog layout with Header/Footer
retur