diff --git a/.claude/skills/nextjs-coding-standards/SKILL.md b/.claude/skills/nextjs-coding-standards/SKILL.md index d5dc7b3..06d9820 100644 --- a/.claude/skills/nextjs-coding-standards/SKILL.md +++ b/.claude/skills/nextjs-coding-standards/SKILL.md @@ -15,6 +15,7 @@ Reference this skill when writing or reviewing code to ensure consistency with p ### Files and Directories **Use kebab-case for all file names:** + ``` ✅ user-profile.tsx ✅ blog-post-card.tsx @@ -26,12 +27,14 @@ Reference this skill when writing or reviewing code to ensure consistency with p ``` **Why kebab-case?** + - Cross-platform compatibility (Windows vs Unix) - URL-friendly (file names often map to routes) - Easier to parse and read - Industry standard for Next.js projects **Special Next.js Files:** + ``` page.tsx # Route pages layout.tsx # Layout components @@ -44,6 +47,7 @@ route.ts # API route handlers ### Component Names (Inside Files) **Use PascalCase for component names:** + ```typescript // File: user-profile.tsx export function UserProfile() { @@ -59,6 +63,7 @@ export default function BlogPostCard() { ### Variables, Functions, Props **Use camelCase:** + ```typescript // Variables const userSettings = {} @@ -83,10 +88,11 @@ function useMarkdown() {} ### Constants **Use SCREAMING_SNAKE_CASE:** + ```typescript -const API_BASE_URL = "https://api.example.com" +const API_BASE_URL = 'https://api.example.com' const MAX_RETRIES = 3 -const DEFAULT_LOCALE = "ro-RO" +const DEFAULT_LOCALE = 'ro-RO' ``` --- @@ -133,10 +139,7 @@ export async function POST(request: NextRequest) { const parsed = bodySchema.safeParse(json) if (!parsed.success) { - return NextResponse.json( - { error: 'Validation failed', details: parsed.error }, - { status: 400 } - ) + return NextResponse.json({ error: 'Validation failed', details: parsed.error }, { status: 400 }) } // parsed.data is fully typed @@ -146,6 +149,7 @@ export async function POST(request: NextRequest) { ``` **Key Points:** + - Use `safeParse()` instead of `parse()` to avoid try/catch - Return structured error responses - Use appropriate HTTP status codes @@ -154,6 +158,7 @@ export async function POST(request: NextRequest) { ### Error Handling **Return meaningful status codes:** + ```typescript 200 // Success 201 // Created @@ -165,12 +170,13 @@ export async function POST(request: NextRequest) { ``` **Structured error responses:** + ```typescript return NextResponse.json( { error: 'Resource not found', code: 'NOT_FOUND', - timestamp: new Date().toISOString() + timestamp: new Date().toISOString(), }, { status: 404 } ) @@ -277,12 +283,12 @@ export function ThemeToggle() { ```javascript // tailwind.config.js module.exports = { - darkMode: 'class', // Required for next-themes + darkMode: 'class', // Required for next-themes theme: { extend: { colors: { 'dark-primary': '#18181b', - 'accent': { + accent: { DEFAULT: '#164e63', hover: '#155e75', }, @@ -380,6 +386,7 @@ export function Card({ children, className = "" }) { ``` **Standard breakpoints:** + ``` sm: 640px // Small tablets md: 768px // Tablets @@ -459,18 +466,21 @@ content/ # Content files (outside app/) ### lib/ Organization **Modules in lib/:** + - Substantial business logic (markdown.ts, seo.ts) - API clients and data fetching - Database connections - Authentication logic **Utils in lib/utils.ts:** + - Pure helper functions - Formatters (formatDate, formatCurrency) - Validators (isEmail, isValidUrl) - String manipulations **Types in lib/types/:** + - Shared TypeScript interfaces - API response types - Domain models @@ -479,6 +489,7 @@ content/ # Content files (outside app/) ### Component Organization **By domain/feature:** + ``` components/ ├── blog/ # Blog-specific @@ -495,6 +506,7 @@ components/ ``` **Not by type:** + ``` ❌ Don't organize like this: components/ @@ -507,6 +519,7 @@ components/ ### Public Assets **Organize by feature:** + ``` public/ ├── blog/ @@ -519,6 +532,7 @@ public/ ``` **Naming conventions:** + - Use descriptive names: `hero-background.jpg` not `img1.jpg` - Use kebab-case: `user-avatar.png` - Include dimensions for images: `logo-512x512.png` @@ -530,9 +544,10 @@ public/ ### Type Safety **Avoid `any`:** + ```typescript // ❌ Bad -function processData(data: any) { } +function processData(data: any) {} // ✅ Good function processData(data: unknown) { @@ -546,7 +561,7 @@ interface PostData { title: string content: string } -function processData(data: PostData) { } +function processData(data: PostData) {} ``` ### Infer Types from Zod @@ -665,6 +680,7 @@ export function InteractiveCard({ title }) { ``` **When to use 'use client':** + - Using React hooks (useState, useEffect, etc.) - Using event handlers (onClick, onChange, etc.) - Using browser APIs (window, localStorage, etc.) @@ -743,7 +759,7 @@ export async function generateStaticParams() { ```typescript // In frontmatter -date: "2025-01-15" +date: '2025-01-15' // For display formatDate(post.frontmatter.date) // "15 ianuarie 2025" (Romanian) diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..8eb48a4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +node_modules +.next +out +build +dist +.cache +package-lock.json +public +coverage diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..db041af --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "printWidth": 100, + "arrowParens": "avoid", + "trailingComma": "es5" +} diff --git a/CLAUDE.md b/CLAUDE.md index 9fbb92f..9905ea2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,15 +74,16 @@ lib/ ``` **Frontmatter Schema:** + ```yaml --- -title: string # Required -description: string # Required -date: "YYYY-MM-DD" # Required, ISO format -author: string # Required -tags: [string, string?, string?] # Max 3 tags -image?: string # Optional hero image -draft?: boolean # Exclude from listings if true +title: string # Required +description: string # Required +date: 'YYYY-MM-DD' # Required, ISO format +author: string # Required +tags: [string, string?, string?] # Max 3 tags +image?: string # Optional hero image +draft?: boolean # Exclude from listings if true --- ``` @@ -107,20 +108,25 @@ components/ ### File Naming Conventions **Files and Directories:** + - Use **kebab-case** for all file names: `user-profile.tsx`, `blog-post.tsx` - Special Next.js files: `page.tsx`, `layout.tsx`, `not-found.tsx`, `loading.tsx` **Component Names (inside files):** + - Use **PascalCase**: `export function UserProfile()`, `export default BlogPost` **Variables, Functions, Props:** + - Use **camelCase**: `const userSettings = {}`, `function handleSubmit() {}` - Hooks: `useTheme`, `useMarkdown` **Constants:** + - Use **SCREAMING_SNAKE_CASE**: `const API_BASE_URL = "..."` **Why kebab-case for files?** + - Cross-platform compatibility (Windows vs Unix) - URL-friendly (file names often map to routes) - Easier to parse and read @@ -152,6 +158,7 @@ export default function RootLayout({ children }) { ``` **Client Component for Toggle:** + ```typescript // components/theme-toggle.tsx 'use client' @@ -173,23 +180,25 @@ export function ThemeToggle() { ``` **Tailwind Configuration:** + ```javascript // tailwind.config.js module.exports = { - darkMode: 'class', // Use 'class' strategy for next-themes + darkMode: 'class', // Use 'class' strategy for next-themes theme: { extend: { colors: { // Define custom colors for consistency 'dark-primary': '#18181b', - 'accent': { DEFAULT: '#164e63', hover: '#155e75' } - } - } - } + accent: { DEFAULT: '#164e63', hover: '#155e75' }, + }, + }, + }, } ``` **CSS Variables Pattern:** + ```css /* globals.css */ :root { @@ -219,6 +228,7 @@ module.exports = { ### Next.js 16 Specific Patterns **Async Server Components:** + ```typescript // app/blog/page.tsx export default async function BlogPage() { @@ -228,6 +238,7 @@ export default async function BlogPage() { ``` **Static Generation with Dynamic Routes:** + ```typescript // app/blog/[...slug]/page.tsx export async function generateStaticParams() { @@ -242,6 +253,7 @@ export async function generateMetadata({ params }) { ``` **Parallel Routes for Layout Composition:** + ```typescript // app/layout.tsx export default function RootLayout({ @@ -278,12 +290,14 @@ export default function RootLayout({ ### Styling Guidelines **Color Palette:** + - Backgrounds: `zinc-900`, `slate-900`, `slate-800` - Accents: `cyan-900`, `emerald-900`, `teal-900` - Text: `slate-100`, `slate-300`, `slate-500` - Borders: `border-2`, `border-4` (thick, sharp) **Design Tokens:** + - **NO rounded corners:** Use `rounded-none` or omit (default is sharp) - **Monospace fonts:** Apply `font-mono` for terminal aesthetic - **Uppercase labels:** Use `uppercase tracking-wider` for headers @@ -291,6 +305,7 @@ export default function RootLayout({ - **Classification labels:** Add metadata like "FILE#001", "DOCUMENT LEVEL-1" **Typography:** + - Primary font: `JetBrains Mono` (monospace) - Headings: `font-mono font-bold uppercase` - Body: `font-mono text-sm` diff --git a/README.md b/README.md index 4a9a9ee..b92c4a6 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -### This is the repo for the actual profile page \ No newline at end of file +### This is the repo for the actual profile page diff --git a/app/@breadcrumbs/about/page.tsx b/app/@breadcrumbs/about/page.tsx index ed78d5e..7584620 100644 --- a/app/@breadcrumbs/about/page.tsx +++ b/app/@breadcrumbs/about/page.tsx @@ -1,4 +1,4 @@ -import { Breadcrumbs } from '@/components/layout/Breadcrumbs'; +import { Breadcrumbs } from '@/components/layout/Breadcrumbs' export default function AboutBreadcrumb() { return ( @@ -11,5 +11,5 @@ export default function AboutBreadcrumb() { }, ]} /> - ); + ) } diff --git a/app/@breadcrumbs/blog/[...slug]/page.tsx b/app/@breadcrumbs/blog/[...slug]/page.tsx index a2dc242..3b700e9 100644 --- a/app/@breadcrumbs/blog/[...slug]/page.tsx +++ b/app/@breadcrumbs/blog/[...slug]/page.tsx @@ -1,10 +1,10 @@ -import { Breadcrumbs } from '@/components/layout/Breadcrumbs'; -import { getPostBySlug } from '@/lib/markdown'; +import { Breadcrumbs } from '@/components/layout/Breadcrumbs' +import { getPostBySlug } from '@/lib/markdown' interface BreadcrumbItem { - label: string; - href: string; - current?: boolean; + label: string + href: string + current?: boolean } function formatDirectoryName(name: string): string { @@ -12,34 +12,34 @@ function formatDirectoryName(name: string): string { tech: 'Tehnologie', design: 'Design', tutorial: 'Tutoriale', - }; + } - return directoryNames[name] || name.charAt(0).toUpperCase() + name.slice(1); + return directoryNames[name] || name.charAt(0).toUpperCase() + name.slice(1) } export default async function BlogPostBreadcrumb({ params, }: { - params: Promise<{ slug: string[] }>; + params: Promise<{ slug: string[] }> }) { - const { slug } = await params; - const slugPath = slug.join('/'); - const post = getPostBySlug(slugPath); + const { slug } = await params + const slugPath = slug.join('/') + const post = getPostBySlug(slugPath) const items: BreadcrumbItem[] = [ { label: 'Blog', href: '/blog', }, - ]; + ] if (slug.length > 1) { for (let i = 0; i < slug.length - 1; i++) { - const segmentPath = slug.slice(0, i + 1).join('/'); + const segmentPath = slug.slice(0, i + 1).join('/') items.push({ label: formatDirectoryName(slug[i]), href: `/blog/${segmentPath}`, - }); + }) } } @@ -47,7 +47,7 @@ export default async function BlogPostBreadcrumb({ label: post ? post.frontmatter.title : slug[slug.length - 1], href: `/blog/${slugPath}`, current: true, - }); + }) - return ; + return } diff --git a/app/@breadcrumbs/blog/page.tsx b/app/@breadcrumbs/blog/page.tsx index 6abd4ba..44e92ed 100644 --- a/app/@breadcrumbs/blog/page.tsx +++ b/app/@breadcrumbs/blog/page.tsx @@ -1,4 +1,4 @@ -import { Breadcrumbs } from '@/components/layout/Breadcrumbs'; +import { Breadcrumbs } from '@/components/layout/Breadcrumbs' export default function BlogBreadcrumb() { return ( @@ -11,5 +11,5 @@ export default function BlogBreadcrumb() { }, ]} /> - ); + ) } diff --git a/app/@breadcrumbs/default.tsx b/app/@breadcrumbs/default.tsx index 8f1d385..ef9aa2e 100644 --- a/app/@breadcrumbs/default.tsx +++ b/app/@breadcrumbs/default.tsx @@ -1,7 +1,7 @@ -'use client'; +'use client' -import { Breadcrumbs } from '@/components/layout/Breadcrumbs'; +import { Breadcrumbs } from '@/components/layout/Breadcrumbs' export default function DefaultBreadcrumb() { - return ; + return } diff --git a/app/@breadcrumbs/tags/[tag]/page.tsx b/app/@breadcrumbs/tags/[tag]/page.tsx index 9451e8d..5348acd 100644 --- a/app/@breadcrumbs/tags/[tag]/page.tsx +++ b/app/@breadcrumbs/tags/[tag]/page.tsx @@ -1,15 +1,11 @@ -import { Breadcrumbs } from '@/components/layout/Breadcrumbs'; +import { Breadcrumbs } from '@/components/layout/Breadcrumbs' -export default async function TagBreadcrumb({ - params, -}: { - params: Promise<{ tag: string }>; -}) { - const { tag } = await params; +export default async function TagBreadcrumb({ params }: { params: Promise<{ tag: string }> }) { + const { tag } = await params const tagName = tag .split('-') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '); + .join(' ') return ( - ); + ) } diff --git a/app/@breadcrumbs/tags/page.tsx b/app/@breadcrumbs/tags/page.tsx index 8d74cd3..c499354 100644 --- a/app/@breadcrumbs/tags/page.tsx +++ b/app/@breadcrumbs/tags/page.tsx @@ -1,4 +1,4 @@ -import { Breadcrumbs } from '@/components/layout/Breadcrumbs'; +import { Breadcrumbs } from '@/components/layout/Breadcrumbs' export default function TagsBreadcrumb() { return ( @@ -11,5 +11,5 @@ export default function TagsBreadcrumb() { }, ]} /> - ); + ) } diff --git a/app/about/page.tsx b/app/about/page.tsx index d8f383c..9870ec7 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -12,8 +12,8 @@ export default function AboutPage() {

- Bun venit pe blogul meu! Sunt un dezvoltator pasionat de tehnologie, - specializat în dezvoltarea web modernă cu Next.js, React și TypeScript. + Bun venit pe blogul meu! Sunt un dezvoltator pasionat de tehnologie, specializat în + dezvoltarea web modernă cu Next.js, React și TypeScript.

Ce vei găsi aici

@@ -27,10 +27,18 @@ export default function AboutPage() {

Tehnologii folosite

Acest blog este construit cu:

    -
  • Next.js 15 - Framework React pentru producție
  • -
  • TypeScript - Pentru type safety
  • -
  • Tailwind CSS - Pentru stilizare rapidă
  • -
  • Markdown - Pentru conținut
  • +
  • + Next.js 15 - Framework React pentru producție +
  • +
  • + TypeScript - Pentru type safety +
  • +
  • + Tailwind CSS - Pentru stilizare rapidă +
  • +
  • + Markdown - Pentru conținut +

Contact

diff --git a/app/blog/[...slug]/not-found.tsx b/app/blog/[...slug]/not-found.tsx index 973ee8c..7aa1f31 100644 --- a/app/blog/[...slug]/not-found.tsx +++ b/app/blog/[...slug]/not-found.tsx @@ -10,10 +10,16 @@ export default function NotFound() { Ne pare rău, dar articolul pe care îl cauți nu există sau a fost mutat.

- + Vezi toate articolele - + Pagina principală
diff --git a/app/blog/[...slug]/page.tsx b/app/blog/[...slug]/page.tsx index b8fe778..1e3b1d8 100644 --- a/app/blog/[...slug]/page.tsx +++ b/app/blog/[...slug]/page.tsx @@ -10,10 +10,14 @@ import MarkdownRenderer from '@/components/blog/markdown-renderer' export async function generateStaticParams() { const posts = await getAllPosts() - return posts.map((post) => ({ slug: post.slug.split('/') })) + return posts.map(post => ({ slug: post.slug.split('/') })) } -export async function generateMetadata({ params }: { params: Promise<{ slug: string[] }> }): Promise { +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string[] }> +}): Promise { const { slug } = await params const slugPath = slug.join('/') const post = getPostBySlug(slugPath) @@ -51,7 +55,10 @@ function extractHeadings(content: string) { while ((match = headingRegex.exec(content)) !== null) { const level = match[1].length const text = match[2] - const id = text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '') + const id = text + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)/g, '') headings.push({ id, text, level }) } @@ -110,7 +117,8 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:

- >> {post.frontmatter.description} + >>{' '} + {post.frontmatter.description}

@@ -121,9 +129,13 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug:
-

{post.frontmatter.author}

+

+ {post.frontmatter.author} +

- + // {post.readingTime}min READ
@@ -147,17 +159,25 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug: {relatedPosts.length > 0 && (
-

// Articole similare

+

+ // Articole similare +

- {relatedPosts.map((relatedPost) => ( + {relatedPosts.map(relatedPost => ( -

{relatedPost.frontmatter.title}

-

{relatedPost.frontmatter.description}

-

{formatDate(relatedPost.frontmatter.date)}

+

+ {relatedPost.frontmatter.title} +

+

+ {relatedPost.frontmatter.description} +

+

+ {formatDate(relatedPost.frontmatter.date)} +

))}
@@ -170,7 +190,12 @@ export default async function BlogPostPage({ params }: { params: Promise<{ slug: className="flex items-center text-[var(--neon-pink)] hover:text-[var(--neon-magenta)] transition-all font-mono text-sm uppercase border border-[var(--neon-pink)] px-4 py-2 hover:shadow-[0_0_6px_rgba(155,90,110,0.3)]" > - + [BACK TO BLOG] diff --git a/app/blog/blog-client.tsx b/app/blog/blog-client.tsx index c44761b..6af0ce7 100644 --- a/app/blog/blog-client.tsx +++ b/app/blog/blog-client.tsx @@ -23,15 +23,14 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) const postsPerPage = 9 const filteredAndSortedPosts = useMemo(() => { - let result = posts.filter((post) => { + const result = posts.filter(post => { const matchesSearch = searchQuery === '' || post.frontmatter.title.toLowerCase().includes(searchQuery.toLowerCase()) || post.frontmatter.description.toLowerCase().includes(searchQuery.toLowerCase()) const matchesTags = - selectedTags.length === 0 || - selectedTags.every((tag) => post.frontmatter.tags.includes(tag)) + selectedTags.length === 0 || selectedTags.every(tag => post.frontmatter.tags.includes(tag)) return matchesSearch && matchesTags }) @@ -58,15 +57,12 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) ) const toggleTag = (tag: string) => { - setSelectedTags((prev) => - prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag] - ) + setSelectedTags(prev => (prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag])) setCurrentPage(1) } return (
-
{/* Header */}
@@ -83,15 +79,12 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
{ + onSearchChange={value => { setSearchQuery(value) setCurrentPage(1) }} /> - +
@@ -109,7 +102,8 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps) {/* Results Count */}

- FOUND {filteredAndSortedPosts.length} {filteredAndSortedPosts.length === 1 ? 'POST' : 'POSTS'} + FOUND {filteredAndSortedPosts.length}{' '} + {filteredAndSortedPosts.length === 1 ? 'POST' : 'POSTS'}

@@ -142,14 +136,14 @@ export default function BlogPageClient({ posts, allTags }: BlogPageClientProps)
- {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => ( + {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => (
diff --git a/components/blog/table-of-contents.tsx b/components/blog/table-of-contents.tsx index a70c9f7..abb7bed 100644 --- a/components/blog/table-of-contents.tsx +++ b/components/blog/table-of-contents.tsx @@ -17,8 +17,8 @@ export function TableOfContents({ headings }: TOCProps) { useEffect(() => { const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { + entries => { + entries.forEach(entry => { if (entry.isIntersecting) { setActiveId(entry.target.id) } @@ -43,31 +43,45 @@ export function TableOfContents({ headings }: TOCProps) {
-
-
-
+
+
+
-

+

>> NAVIGATION

diff --git a/components/blog/tag-filter.tsx b/components/blog/tag-filter.tsx index deebbcf..91756b4 100644 --- a/components/blog/tag-filter.tsx +++ b/components/blog/tag-filter.tsx @@ -14,7 +14,7 @@ export function TagFilter({ allTags, selectedTags, onToggleTag, onClearTags }: T FILTER BY TAG

- {allTags.map((tag) => ( + {allTags.map(tag => (