16 KiB
Contributing to Portfolio
Thank you for your interest in contributing to this project! This guide will help you get started with development.
Table of Contents
- Getting Started
- Project Overview
- Tech Stack
- Project Structure
- Development Workflow
- Code Style & Standards
- Testing
- Commit Guidelines
Getting Started
Prerequisites
- Node.js 18.x or higher
- npm 9.x or higher
- Git for version control
Installation
-
Clone the repository
git clone <repository-url> cd portfolio -
Install dependencies
npm install -
Set up environment variables
Create a
.env.localfile in the root directory with the required environment variables:# Supabase Configuration NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key # Optional: OpenAI (for image generation scripts) OPENAI_API_KEY=your_openai_api_key -
Start the development server
npm run devThe application will be available at
http://localhost:3000
Available Scripts
| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm run build |
Build production bundle |
npm start |
Start production server (after build) |
npm run lint |
Run ESLint with strict mode |
npm test |
Run unit tests with Vitest |
npm run test:coverage |
Run tests with coverage report |
npm run build:images |
Optimize images using Sharp |
npm run generate:images |
Generate blog images using OpenAI |
npm run generate:images:dry |
Preview image generation without creating files |
Project Overview
This is a personal portfolio website for Damjan Savić, showcasing work as an AI & Automation Specialist. The site features:
- Multi-language support - German, English, and Serbian with automatic language detection
- Progressive Web App - Installable and offline-capable
- SEO optimized - Structured data (Schema.org), sitemap generation
- Responsive Design - Mobile-first approach with Tailwind CSS
- Blog System - MDX-based content with frontmatter support
- Portfolio Gallery - Dynamic project showcase with filtering
- Contact Form - Integrated with Supabase backend
Tech Stack
Core Framework
- Next.js 15 - React framework with App Router
- React 19 - UI library with latest features
- TypeScript 5.5 - Type-safe development
Styling & UI
- Tailwind CSS 3.4 - Utility-first CSS framework
- Framer Motion 11 - Animation library
- Lucide React - Icon library
- Tailwind Merge - Utility for merging Tailwind classes
- CVA (Class Variance Authority) - Component variants management
Content & Internationalization
- MDX - Markdown + JSX for content pages
@mdx-js/loader,@mdx-js/mdx,@mdx-js/react@next/mdx,next-mdx-remote
- next-intl 3.26 - Internationalization (i18n)
- gray-matter - Frontmatter parsing
Backend & Services
- Supabase - Database, authentication, and backend services
@supabase/supabase-js@supabase/ssr
- OpenAI 4.77 - AI integration for content generation
Development Tools
- Vitest - Unit testing framework
- ESLint 9 - Code linting with Next.js config
- PostCSS - CSS processing
- Autoprefixer - CSS vendor prefixes
- Sharp - Image optimization
Performance & Analytics
- web-vitals - Performance metrics
- react-intersection-observer - Lazy loading and scroll animations
Project Structure
portfolio/
├── public/ # Static assets (images, fonts, etc.)
├── scripts/ # Build and utility scripts
│ ├── optimize-images.js # Image optimization
│ └── generate-blog-images.mjs # AI-powered image generation
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── [locale]/ # Internationalized routes
│ │ │ ├── about/ # About page
│ │ │ ├── blog/ # Blog listing & posts
│ │ │ ├── contact/ # Contact form
│ │ │ ├── dashboard/ # Admin dashboard
│ │ │ ├── imprint/ # Legal imprint
│ │ │ ├── leistungen/ # Services page
│ │ │ ├── login/ # Authentication
│ │ │ ├── portfolio/ # Portfolio gallery & projects
│ │ │ ├── privacy/ # Privacy policy
│ │ │ ├── terms/ # Terms of service
│ │ │ ├── layout.tsx # Locale-specific layout
│ │ │ └── page.tsx # Homepage
│ │ ├── globals.css # Global styles
│ │ ├── layout.tsx # Root layout
│ │ ├── not-found.tsx # 404 page
│ │ ├── robots.ts # Robots.txt generation
│ │ └── sitemap.ts # Sitemap generation
│ ├── components/ # Reusable React components
│ ├── components-vite/ # Legacy Vite components
│ ├── data/ # Static data and content
│ ├── hooks/ # Custom React hooks
│ ├── i18n/ # Internationalization config
│ ├── lib/ # Utility libraries and helpers
│ ├── messages/ # Translation files (de, en, sr)
│ ├── middleware.ts # Next.js middleware (i18n routing)
│ ├── pages-vite/ # Legacy Vite pages
│ ├── services/ # API and service integrations
│ ├── styles/ # Additional styles
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Helper functions
├── .env.local # Environment variables (not in git)
├── .eslintrc.json # ESLint configuration
├── docker-compose.yml # Docker Compose setup
├── Dockerfile # Docker container config
├── next.config.ts # Next.js configuration
├── package.json # Dependencies and scripts
├── postcss.config.js # PostCSS configuration
├── tailwind.config.js # Tailwind CSS configuration
└── tsconfig.json # TypeScript configuration
Key Directories Explained
src/app/- Next.js App Router pages and layouts using the new file-based routing systemsrc/app/[locale]/- All pages are nested under locale for multi-language support (de, en, sr)src/components/- Reusable UI components (buttons, cards, forms, navigation, etc.)src/hooks/- Custom React hooks for shared logicsrc/lib/- Utility libraries (Supabase client, helpers, etc.)src/messages/- Translation JSON files for each languagesrc/services/- API integrations and external service wrapperssrc/types/- TypeScript interfaces and type definitionssrc/utils/- Helper functions and utilitiespublic/- Static files served directly (images, fonts, favicons)scripts/- Build automation and utility scripts
Development Workflow
1. Create a New Feature Branch
git checkout -b feature/your-feature-name
2. Make Your Changes
- Follow the existing code patterns and structure
- Use TypeScript for type safety
- Write clean, readable code with appropriate comments
- Test your changes locally
3. Test Your Changes
# Run development server
npm run dev
# Run linter
npm run lint
# Run tests
npm test
4. Build for Production
npm run build
Ensure the build completes without errors before submitting.
Code Style & Standards
ESLint & Code Linting
This project uses ESLint to enforce code quality and consistency. The configuration extends Next.js recommended presets:
ESLint Configuration (.eslintrc.json):
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}
Key ESLint Rules:
next/core-web-vitals- Enforces Next.js best practices and Core Web Vitals optimizationsnext/typescript- TypeScript-specific rules for Next.js applications
Running the Linter:
# Run ESLint in strict mode (recommended before committing)
npm run lint
# The linter will check for:
# - Code quality issues
# - Next.js best practices violations
# - TypeScript type errors
# - Unused variables and imports
# - Accessibility issues
Important:
- Always run
npm run lintbefore committing changes - Fix all linting errors - the project uses
--strictmode - The linter runs automatically during the build process
- ESLint errors will prevent successful production builds
Code Formatting
General Formatting Guidelines:
- Indentation: 2 spaces (no tabs)
- Line Length: Aim for 80-100 characters, hard limit at 120
- Semicolons: Required at the end of statements
- Quotes: Single quotes for strings (except in JSX/TSX where double quotes are preferred)
- Trailing Commas: Use trailing commas in multi-line objects and arrays
Example:
// ✅ Good
const user = {
name: 'John Doe',
email: 'john@example.com',
role: 'admin',
};
// ❌ Bad
const user = {
name: "John Doe",
email: "john@example.com",
role: "admin"
}
TypeScript
- Use TypeScript for all new files
- Define proper types and interfaces
- Avoid using
any- use proper typing orunknown - Export types from a central
types/directory when shared
React Components
- Use functional components with hooks
- Follow the component structure:
- Imports
- Type definitions
- Component definition
- Helper functions (if needed)
- Exports
Example:
import { useState } from 'react';
import { Button } from '@/components/ui/Button';
interface MyComponentProps {
title: string;
onSubmit: () => void;
}
export function MyComponent({ title, onSubmit }: MyComponentProps) {
const [isLoading, setIsLoading] = useState(false);
const handleClick = async () => {
setIsLoading(true);
await onSubmit();
setIsLoading(false);
};
return (
<div>
<h2>{title}</h2>
<Button onClick={handleClick} disabled={isLoading}>
Submit
</Button>
</div>
);
}
Styling
- Use Tailwind CSS utility classes
- Use
cn()utility fromtailwind-mergeto merge classes - Follow the mobile-first responsive design approach
- Use CSS variables for theme colors (defined in
globals.css)
File Naming
- Components: PascalCase (e.g.,
MyComponent.tsx) - Utilities: camelCase (e.g.,
formatDate.ts) - Pages: lowercase (e.g.,
page.tsx,layout.tsx) - Types: PascalCase with
.types.tssuffix (e.g.,User.types.ts)
Import Organization
// 1. External dependencies
import { useState } from 'react';
import { useTranslations } from 'next-intl';
// 2. Internal components and utilities
import { Button } from '@/components/ui/Button';
import { formatDate } from '@/utils/date';
// 3. Types
import type { User } from '@/types/User.types';
// 4. Styles (if any)
import styles from './Component.module.css';
Internationalization
- All user-facing text must be internationalized
- Use
useTranslations()hook fromnext-intl - Add translation keys to all language files in
src/messages/
Example:
import { useTranslations } from 'next-intl';
export function WelcomeMessage() {
const t = useTranslations('home');
return <h1>{t('welcome')}</h1>;
}
Testing
This project uses Vitest as its testing framework. Vitest is a fast, modern test runner built for Vite-based projects, offering:
- Lightning-fast execution with native ESM support
- Jest-compatible API for easy migration and familiarity
- First-class TypeScript support
- Built-in coverage reporting with c8
Current State: The project is fully configured with Vitest, but no tests currently exist. We encourage contributors to add tests for new features and gradually improve test coverage.
Test Setup
Vitest is already configured in package.json:
{
"scripts": {
"test": "vitest",
"test:coverage": "vitest run --coverage"
},
"devDependencies": {
"vitest": "^1.3.1"
}
}
Running Tests
# Run tests in watch mode (recommended during development)
npm test
# Run tests once (useful for CI/CD)
npm run test:coverage
# The test runner will:
# - Automatically detect .test.ts, .test.tsx, .spec.ts, .spec.tsx files
# - Re-run tests when files change (in watch mode)
# - Display coverage reports (with --coverage flag)
Writing Tests
When adding tests to this project, follow these conventions:
1. File Naming & Location
- Place test files next to the code they test
- Use
.test.tsor.test.tsxextension for test files - Match the filename of the file being tested
Example:
src/utils/
├── formatDate.ts # Source file
└── formatDate.test.ts # Test file
2. Test Structure
- Use
describeblocks to group related tests - Use
itortestfor individual test cases - Write descriptive test names that explain the expected behavior
Example:
// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate } from './formatDate';
describe('formatDate', () => {
it('formats date in DD.MM.YYYY format', () => {
const date = new Date('2024-01-15');
expect(formatDate(date)).toBe('15.01.2024');
});
it('handles invalid dates gracefully', () => {
const invalidDate = new Date('invalid');
expect(formatDate(invalidDate)).toBe('Invalid Date');
});
});
3. Component Testing
For React components, use Vitest with React Testing Library patterns:
// src/components/Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
});
4. What to Test
- Utility functions - Pure functions and helpers
- Business logic - Complex calculations and transformations
- Hooks - Custom React hooks with multiple states
- Components - User interactions and rendering logic
- API integrations - Service layer functions (use mocks)
5. Testing Conventions
- Arrange-Act-Assert pattern for test structure
- One assertion per test when possible (for clarity)
- Mock external dependencies (API calls, database, etc.)
- Test edge cases and error conditions
- Use meaningful test data that reflects real usage
Example:
describe('calculateDiscount', () => {
it('applies 10% discount for orders over $100', () => {
// Arrange
const orderTotal = 150;
const discountThreshold = 100;
// Act
const result = calculateDiscount(orderTotal, discountThreshold);
// Assert
expect(result).toBe(135); // 150 - 15 (10%)
});
});
Test Coverage Goals
While we don't enforce strict coverage requirements, aim for:
- 80%+ coverage for utility functions and business logic
- 60%+ coverage for components
- 100% coverage for critical paths (authentication, payments, data validation)
Run npm run test:coverage to see current coverage reports.
Commit Guidelines
Commit Message Format
<type>: <subject>
<body>
Types
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting, etc.)refactor- Code refactoringtest- Adding or updating testschore- Maintenance tasks
Examples
git commit -m "feat: add contact form validation"
git commit -m "fix: resolve mobile navigation menu bug"
git commit -m "docs: update README with deployment instructions"
Questions?
If you have any questions or need help, please:
- Check existing documentation
- Review similar code in the codebase
- Open an issue for discussion
Built with Next.js + TypeScript + Tailwind CSS