Merge pull request #2 from KuchtaVR6/modern-project-configuration

Modern Project Configuration
This commit is contained in:
Patryk Kuchta
2026-02-22 18:30:34 +00:00
committed by GitHub
53 changed files with 5094 additions and 5450 deletions

View File

@@ -28,6 +28,87 @@
"error", "error",
"double" "double"
], ],
"semi": "error" "semi": "error",
"no-console": "warn",
"no-debugger": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": true,
"allowTypedFunctionExpressions": true
}
],
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": ["PascalCase"],
"prefix": ["I"]
},
{
"selector": "typeAlias",
"format": ["PascalCase"]
}
],
"react/prop-types": "off",
"react/react-in-jsx-scope": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"no-var": "error",
"prefer-const": "error",
"prefer-arrow-callback": "error",
"no-trailing-spaces": "error",
"eol-last": ["error", "always"],
"object-curly-spacing": ["error", "always"],
"array-bracket-spacing": ["error", "never"],
"comma-dangle": ["error", "never"],
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }],
"arrow-spacing": ["error", { "before": true, "after": true }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"max-len": [
"error",
{
"code": 120,
"tabWidth": 4,
"ignoreUrls": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true,
"ignoreRegExpLiterals": true,
"ignoreComments": false
}
],
"max-lines": [
"error",
{
"max": 300,
"skipBlankLines": true,
"skipComments": true
}
],
"max-lines-per-function": [
"error",
{
"max": 50,
"skipBlankLines": true,
"skipComments": true,
"IIFEs": true
}
],
"complexity": ["error", 10],
"max-depth": ["error", 4],
"max-params": ["error", 4],
"max-nested-callbacks": ["error", 3],
"max-statements": ["error", 20, { "ignoreTopLevelFunctions": false }]
} }
} }

73
.github/workflows/security-audit.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: Security Audit
on:
push:
branches:
- main
- master
- develop
pull_request:
branches:
- main
- master
- develop
schedule:
- cron: '0 0 * * 1'
jobs:
security-audit:
name: Security Vulnerability Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run pnpm audit
run: pnpm audit --audit-level=moderate
continue-on-error: true
- name: Generate security report
run: pnpm run security:check
- name: Check for outdated dependencies
run: pnpm outdated || true
- name: Upload security report
uses: actions/upload-artifact@v4
if: always()
with:
name: security-report
path: security-report.json
retention-days: 30
- name: Notify on high/critical vulnerabilities
if: failure()
run: |
echo "::error::Security vulnerabilities detected! Check the security report artifact for details."

71
.github/workflows/typecheck.yml vendored Normal file
View File

@@ -0,0 +1,71 @@
name: Code Quality Check
on:
push:
branches:
- main
- master
- develop
pull_request:
branches:
- main
- master
- develop
jobs:
quality-check:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm run lint
- name: Run TypeScript type check
run: pnpm run typecheck
- name: Comment PR on failure
if: failure() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '❌ Code quality check failed. Please run `./windsurf-commands.sh lintfix` and `./windsurf-commands.sh typecheck` locally to fix issues.'
})
- name: Notify on failure
if: failure()
run: |
echo "::error::Code quality check failed. Please fix linting and type errors before merging."

9
.gitignore vendored
View File

@@ -4,6 +4,8 @@
/node_modules /node_modules
/.pnp /.pnp
.pnp.js .pnp.js
.pnpm-store
pnpm-debug.log*
# testing # testing
/coverage /coverage
@@ -33,3 +35,10 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# security
security-report.json
*.audit.json
# docker
.dockerignore

1
.node-version Normal file
View File

@@ -0,0 +1 @@
18

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
18

421
AGENTS.md Normal file
View File

@@ -0,0 +1,421 @@
# Portfolio Project Development Rules
## Code Style & Formatting
### TypeScript
- **Tab indentation** (not spaces), **double quotes**, **semicolons** required
- **Explicit function return types** for all functions (except inline expressions)
- **Never use `any` type** - always provide proper typing
- Prefix **interfaces with `I`** (e.g., `IProps`), **PascalCase** for type aliases
- Underscore prefix for unused variables (e.g., `_unusedParam`)
- Avoid non-null assertions (`!`) - use proper null checks
### React & JSX
- Components return `JSX.Element`, functional components with hooks only
- Self-closing tags for components without children: `<Intro/>`
- No prop-types (using TypeScript)
### Spacing & Formatting
- **Object curly spacing**: `{ foo }`, **no array bracket spacing**: `[1, 2]`
- **No trailing commas**, arrow spacing: `() => {}`
- Max **1 empty line** between blocks, end files with newline, no trailing spaces
### Imports
- Use path alias `@/*` (configured in tsconfig)
- Group: external deps first, then internal modules
- Import from `@/src/portfolio/...` and `@/src/styles/...`
## Project Structure
### File Organization
- **Pages**: `/pages/*.tsx` (pages router, not app router)
- **Components**: `/src/portfolio/sections/*.tsx`
- **Styles**: `/src/styles/*.scss` with helpers
- **Data**: `/src/portfolio/data/`, **Helpers**: `/src/portfolio/helpers/`
- **Public**: `/public/` (PDFs, images, icons)
### Component Structure
- Section components in `/src/portfolio/sections/`, main page in `/src/portfolio/MainPage.tsx`
- Each section has single responsibility
## Styling & Libraries
### SCSS
- Import helpers: `@import "helpers";`, use variables: `$accent_colour`
- BEM-like naming, `!important` only for library overrides (Splide)
### Animations
- **AOS** (scroll), **Anime.js** (complex), **react-transition-group** (transitions)
- Respect `prefers-reduced-motion`
### Tech Stack
- **Next.js 15** (pages router), **React 18.2**, **TypeScript 5.1**, **SASS**
- **@splidejs/react-splide**, **react-icons**, **Sharp**
- Dev server on **port 4000**: `npm run dev`
## Development Workflow
### Windsurf Commands Helper Script
The project includes `windsurf-commands.sh` for common development tasks:
```bash
./windsurf-commands.sh lint # Run ESLint to check for code issues
./windsurf-commands.sh lintfix # Run ESLint with auto-fix enabled
./windsurf-commands.sh typecheck # Run TypeScript type checking
./windsurf-commands.sh security:audit # Run security vulnerability scan
./windsurf-commands.sh security:check # Generate security report (JSON)
./windsurf-commands.sh security:outdated # Check for outdated dependencies
./windsurf-commands.sh help # Show available commands
```
**Usage:**
- Script provides consistent interface for development commands
- All commands use `pnpm` internally
- Includes error handling and user-friendly output with emojis
- Avoid using `pnpm run` commands directly
## File Naming
- **Components**: PascalCase (`MainPage.tsx`)
- **Utilities**: camelCase (`helpers.ts`)
- **Styles**: kebab-case/camelCase (`main.scss`)
## Common Patterns
```typescript
// Component
const ComponentName = (): JSX.Element => {
return <div>{/* content */}</div>;
};
export default ComponentName;
// Interface
interface IComponentProps {
title: string;
isActive: boolean;
}
```
```scss
// SCSS
@import "helpers";
.component { color: $accent_colour; }
```
## Code Complexity Constraints
### Strict Limits (ESLint Enforced)
- **Max line length**: 120 characters (ignores URLs, strings, template literals)
- **Max file length**: 300 lines (excluding blank lines and comments)
- **Max function length**: 50 lines (excluding blank lines and comments)
- **Max cyclomatic complexity**: 10 per function
- **Max nesting depth**: 4 levels
- **Max parameters**: 4 per function
- **Max nested callbacks**: 3 levels
- **Max statements**: 20 per function
### Refactoring Strategies
When files exceed limits, use these approaches:
1. **Split by logical sections**: Break large files into feature folders with main component, sub-components, helpers, types, and index.ts
2. **Extract helper functions**: Move utility functions to separate `helpers.ts` files
3. **Extract types**: Move interfaces/types to separate `types.ts` files
4. **Break down complex functions**: Use sub-functions, early returns, extract conditionals, use lookup objects
**Folder structure example:**
```
/src/portfolio/sections/Experience/
Experience.tsx, ExperienceCard.tsx, helpers.ts, types.ts, index.ts
```
**Handling violations:**
1. Analyze what limit was exceeded
2. Choose appropriate strategy
3. Plan folder structure
4. Execute incrementally (create files, move code, update imports, test)
5. Verify with `npm run lint`
**Line length fixes:** Break strings, split chains, extract variables, reformat JSX
**Parameter count fixes:** Use object parameters, configuration objects, or split function
## QA and Testing
### Pre-Testing Checklist
Before running any tests, verify the development server is running:
1. **Check if dev server is running** using `lsof`:
```bash
lsof -i :4000
```
- If output shows a process, server is running
- If no output, start the server with `npm run dev`
2. **Verify server accessibility**:
```bash
curl -I http://localhost:4000
```
- Should return HTTP 200 OK
- If connection refused, server needs to be started
### Playwright MCP Testing Workflow
When testing changes or new features, follow this systematic approach:
#### Step 1: Environment Setup
1. Ensure dev server is running on port 4000
2. Install Playwright browsers if needed (use `mcp0_browser_install` if errors occur)
3. Navigate to `http://localhost:4000`
#### Step 2: Visual Testing
Use Playwright MCP to verify visual elements:
1. **Take accessibility snapshot** (`mcp0_browser_snapshot`):
- Captures page structure and content
- Better than screenshots for testing
- Use this to verify all sections are present
2. **Check key sections are visible**:
- Intro section with name/title
- Achievements section
- Experience section with carousel
- Projects section
- Skills and Links section
- Footer
3. **Verify responsive behavior**:
- Test at different viewport sizes using `mcp0_browser_resize`
- Common breakpoints: 375px (mobile), 768px (tablet), 1920px (desktop)
- Check that content adapts properly
#### Step 3: Functional Testing
Test interactive elements:
1. **Navigation and scrolling**:
- Verify smooth scroll behavior (if `prefers-reduced-motion: no-preference`)
- Test anchor links if present
- Check scroll-triggered animations (AOS)
2. **Carousel functionality** (Splide):
- Click next/previous arrows using `mcp0_browser_click`
- Verify slides change correctly
- Check pagination dots are clickable
- Ensure carousel is responsive
3. **Interactive elements**:
- Test any buttons, links, or accordions
- Verify hover states work
- Check click handlers fire correctly
- Test form inputs if present
4. **External links**:
- Verify PDF links open correctly (CV, research papers)
- Check social media links
- Ensure `target="_blank"` and `rel="noopener noreferrer"` where needed
#### Step 4: Accessibility Testing
1. **Contrast ratios**:
- Use `mcp0_browser_evaluate` to check computed styles
- Verify text has sufficient contrast against backgrounds
- Check accent color (`$accent_colour`) meets WCAG AA standards (4.5:1 for normal text)
2. **Keyboard navigation**:
- Use `mcp0_browser_press_key` to test Tab navigation
- Verify focus indicators are visible
- Check all interactive elements are keyboard accessible
- Test Escape key for modals/overlays
3. **ARIA labels and semantic HTML**:
- Check snapshot for proper heading hierarchy (h1, h2, h3)
- Verify buttons have accessible names
- Check images have alt text
- Ensure landmarks are properly labeled
4. **Screen reader compatibility**:
- Verify meaningful content order in accessibility snapshot
- Check that hidden decorative elements are properly hidden
- Ensure dynamic content updates are announced
#### Step 5: Performance Checks
1. **Console errors**:
- Use `mcp0_browser_console_messages` to check for errors
- Verify no React warnings in development
- Check for missing image errors
- Look for failed network requests
2. **Network requests**:
- Use `mcp0_browser_network_requests` to inspect requests
- Verify images are optimized (using Next.js Image)
- Check for unnecessary requests
- Ensure no 404s or failed requests
3. **Animation performance**:
- Verify AOS animations trigger smoothly
- Check Anime.js animations don't cause jank
- Test on reduced motion preference
#### Step 6: Cross-Section Integration
1. **Section transitions**:
- Scroll through all sections
- Verify spacing between sections is consistent
- Check that animations don't overlap or conflict
2. **Data consistency**:
- Verify project data displays correctly
- Check experience timeline is accurate
- Ensure skills list is complete and categorized
3. **Asset loading**:
- Verify all images load (profile, project screenshots)
- Check PDFs are accessible
- Ensure icons render correctly (react-icons)
### Testing Checklist Template
When testing a new feature or change, use this checklist:
- [ ] Dev server running on port 4000
- [ ] Page loads without errors (check console)
- [ ] All sections render correctly (accessibility snapshot)
- [ ] Responsive at mobile (375px), tablet (768px), desktop (1920px)
- [ ] Interactive elements work (carousels, buttons, links)
- [ ] Keyboard navigation functional (Tab, Enter, Escape)
- [ ] Contrast ratios meet WCAG AA standards
- [ ] No console errors or warnings
- [ ] Network requests successful (no 404s)
- [ ] Animations smooth and respect reduced motion
- [ ] External links open correctly
- [ ] Images optimized and load properly
### Common Test Scenarios
#### Testing a New Section Component
```typescript
// 1. Check server is running
lsof -i :4000
// 2. Navigate and snapshot
mcp0_browser_navigate → http://localhost:4000
mcp0_browser_snapshot
// 3. Verify section appears
// Look for section in snapshot output
// 4. Test responsiveness
mcp0_browser_resize → 375x667 (mobile)
mcp0_browser_snapshot
mcp0_browser_resize → 1920x1080 (desktop)
mcp0_browser_snapshot
// 5. Check console
mcp0_browser_console_messages
```
#### Testing Accessibility
```typescript
// 1. Check contrast
mcp0_browser_evaluate →
function: () => {
const el = document.querySelector('.your-element');
const styles = window.getComputedStyle(el);
return {
color: styles.color,
backgroundColor: styles.backgroundColor
};
}
// 2. Test keyboard navigation
mcp0_browser_press_key → Tab
mcp0_browser_snapshot → verify focus
mcp0_browser_press_key → Enter
mcp0_browser_snapshot → verify action
// 3. Check ARIA
mcp0_browser_snapshot → review for proper labels
```
### When to Run Tests
- **Before committing**: Run basic visual and console checks
- **After adding new section**: Full section integration test
- **After styling changes**: Visual + contrast + responsive tests
- **After adding interactivity**: Functional + keyboard + accessibility tests
- **Before production build**: Complete checklist + all scenarios
### Debugging Failed Tests
1. **Visual issues**: Take screenshot with `mcp0_browser_take_screenshot` for detailed inspection
2. **Functional issues**: Use `mcp0_browser_evaluate` to inspect element state
3. **Network issues**: Check `mcp0_browser_network_requests` for failed requests
4. **Console errors**: Review `mcp0_browser_console_messages` for stack traces
5. **Timing issues**: Use `mcp0_browser_wait_for` to wait for elements/animations
## CI/CD Pipelines
The project includes automated GitHub Actions workflows for code quality and security:
### Lint Check Workflows
- **Push Lint Check** (`.github/workflows/push-lint-check.yml`)
- Runs on every push to any branch
- Executes ESLint checks
- Uses pnpm v10 with caching for faster builds
- Fails if linting errors are found
- **PR Lint Check** (`.github/workflows/pr-lint-check.yml`)
- Runs on pull requests to main/master/develop
- Executes ESLint checks
- Posts comment on PR if linting fails
- Uses pnpm v10 with caching
### TypeScript Type Check Workflow
- **TypeScript Type Check** (`.github/workflows/typecheck.yml`)
- Runs on every push and pull request
- Executes `tsc --noEmit` to check for type errors
- Uses pnpm v10 with caching
- Fails if type errors are found
### Security Audit Workflow
- **Security Audit** (`.github/workflows/security-audit.yml`)
- Runs on push to main/master/develop
- Runs on all pull requests
- Runs weekly on Monday at 00:00 UTC (scheduled)
- Executes `pnpm audit` for vulnerability scanning
- Generates security report (JSON) as artifact
- Checks for outdated dependencies
- Uses pnpm v10 with caching
- Continues on error but uploads report for review
### Pipeline Requirements
All workflows require:
- Node.js 18
- pnpm v10 (matches lockfile version)
- Frozen lockfile (`pnpm install --frozen-lockfile`)
- Proper caching of pnpm store for performance
### Docker Deployment
- **Dockerfile**: Multi-stage build with Alpine Linux base
- Uses pnpm v10.30.1
- Non-root user (UID 1001)
- Read-only filesystem with security hardening
- Standalone Next.js output (~150MB image)
- **docker-compose.yml**: One-command deployment
```bash
docker-compose up -d
```
- Includes health checks
- Security options (no-new-privileges, dropped capabilities)
- Runs on port 4000
## Notes
- This is a **portfolio website** showcasing projects, experience, achievements, and skills
- Runs on **port 4000** (not default 3000)
- Uses **pages router** (not app router)
- Strict TypeScript and ESLint configuration for code quality
- Tab-based indentation is non-negotiable (project standard)
- **Code complexity limits are strictly enforced** - refactor proactively
- **Always test changes with Playwright MCP** before committing
- **IMPORTANT: Always use Windsurf rules** - they are allowed by default and enable much faster AI development progress

46
Dockerfile Normal file
View File

@@ -0,0 +1,46 @@
FROM node:18-alpine AS base
RUN apk add --no-cache libc6-compat
RUN corepack enable && corepack prepare pnpm@10.30.1 --activate
WORKDIR /app
FROM base AS deps
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod=false
FROM base AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN pnpm run build
FROM base AS runner
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
RUN mkdir .next
RUN chown nextjs:nodejs .next
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 4000
ENV PORT=4000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@@ -1,23 +1,87 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Package Manager
This project uses **PNPM** for dependency management, providing better disk space efficiency and faster installs through proper library linking.
### Installation
If you don't have PNPM installed:
```bash
npm install -g pnpm
```
### Install Dependencies
```bash
pnpm install
```
## Getting Started ## Getting Started
First, run the development server: ### Development Server
Run the development server:
```bash ```bash
npm run dev
# or
yarn dev
# or
pnpm dev pnpm dev
``` ```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. Open [http://localhost:4000](http://localhost:4000) with your browser to see the result.
You can start editing the page by modifying `app/index.tsx`. The page auto-updates as you edit the file. You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
### Production Build
Build the application for production:
```bash
pnpm build
pnpm start
```
## Docker Deployment
```bash
docker-compose up -d
```
Stop with `docker-compose down`.
## Working on Multiple Features (Git Worktrees)
Git worktrees allow you to work on multiple branches simultaneously without switching contexts or stashing changes.
### Create a new worktree
```bash
git worktree add ../Portfolio_Remake-feature-name feature-branch-name
```
### Navigate and install dependencies
```bash
cd ../Portfolio_Remake-feature-name
pnpm install
```
### List all worktrees
```bash
git worktree list
```
### Remove a worktree when done
```bash
git worktree remove ../Portfolio_Remake-feature-name
```
**Tip:** Run dev servers on different ports (e.g., main on `:4000`, feature on `:4001`) to work on both simultaneously.
## Learn More ## Learn More
To learn more about Next.js, take a look at the following resources: To learn more about Next.js, take a look at the following resources:
@@ -27,8 +91,15 @@ To learn more about Next.js, take a look at the following resources:
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel ## Available Scripts
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. | Script | Description |
|--------|-------------|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. | `pnpm dev` | Start development server on port 4000 |
| `pnpm build` | Build production application |
| `pnpm start` | Start production server |
| `pnpm lint` | Run ESLint checks |
| `pnpm lintfix` | Auto-fix ESLint issues |
| `pnpm security:audit` | Run security vulnerability scan |
| `pnpm security:check` | Generate security report |
| `pnpm security:outdated` | Check for outdated dependencies |

29
docker-compose.yml Normal file
View File

@@ -0,0 +1,29 @@
services:
portfolio:
build:
context: .
dockerfile: Dockerfile
image: portfolio:latest
container_name: portfolio-app
restart: unless-stopped
ports:
- "4000:4000"
environment:
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "--spider", "http://localhost:4000"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /app/.next/cache
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE

View File

@@ -3,6 +3,46 @@ const path = require("path");
module.exports = { module.exports = {
sassOptions: { sassOptions: {
includePaths: [path.join(__dirname, "styles")], includePaths: [path.join(__dirname, "styles")]
}, },
output: "standalone",
poweredByHeader: false,
compress: true,
async headers() {
return [
{
source: "/:path*",
headers: [
{
key: "X-DNS-Prefetch-Control",
value: "on"
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload"
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN"
},
{
key: "X-Content-Type-Options",
value: "nosniff"
},
{
key: "X-XSS-Protection",
value: "1; mode=block"
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin"
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()"
}
]
}
];
}
}; };

5126
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,33 +6,47 @@
"dev": "next dev -p 4000", "dev": "next dev -p 4000",
"build": "next build", "build": "next build",
"start": "next start -p 4000", "start": "next start -p 4000",
"lint": "next lint" "lint": "next lint",
"lintfix": "next lint --fix",
"typecheck": "tsc --noEmit",
"security:audit": "pnpm audit --audit-level=moderate",
"security:check": "pnpm audit --json > security-report.json || true",
"security:outdated": "pnpm outdated"
}, },
"dependencies": { "dependencies": {
"@splidejs/react-splide": "^0.7.12", "@splidejs/react-splide": "^0.7.12",
"animejs": "^3.2.1", "animejs": "^3.2.2",
"aos": "^2.3.4", "aos": "^2.3.4",
"eslint": "8.42.0", "eslint": "8.57.1",
"eslint-config-next": "13.4.5", "eslint-config-next": "15.5.12",
"next": "^15.0.5", "next": "^15.5.12",
"react": "^18.2.0", "react": "^18.3.1",
"react-dom": "^18.2.0", "react-dom": "^18.3.1",
"react-icons": "^4.9.0", "react-icons": "^5.5.0",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"sharp": "^0.32.4", "sharp": "^0.34.5",
"typescript": "5.1.3" "typescript": "5.9.3"
}, },
"devDependencies": { "devDependencies": {
"@next/eslint-plugin-next": "^13.4.12", "@next/eslint-plugin-next": "^15.5.12",
"@types/aos": "^3.0.4", "@types/aos": "^3.0.7",
"@types/node": "^20.4.8", "@types/node": "^20.19.33",
"@types/react": "^18.2.18", "@types/react": "^18.3.28",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.3.7",
"@types/react-transition-group": "^4.4.10", "@types/react-transition-group": "^4.4.12",
"@typescript-eslint/eslint-plugin": "^6.2.1", "@typescript-eslint/eslint-plugin": "^8.56.0",
"@typescript-eslint/parser": "^6.2.1", "@typescript-eslint/parser": "^8.56.0",
"eslint-plugin-react": "^7.33.0", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^5.2.0",
"sass": "^1.64.1" "sass": "^1.97.3"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"pnpm": {
"overrides": {
"minimatch": ">=10.2.1"
}
} }
} }

View File

@@ -8,11 +8,11 @@ import "@splidejs/splide/css/sea-green";
import Head from "next/head"; import Head from "next/head";
export default function MyApp({ Component, pageProps }: AppProps) { export default function MyApp({ Component, pageProps }: AppProps): JSX.Element {
useEffect(() => { useEffect(() => {
// here you can add your aos options // here you can add your aos options
AOS.init({ AOS.init({
offset: 100, offset: 100
}); });
}, []); }, []);
@@ -42,4 +42,4 @@ export default function MyApp({ Component, pageProps }: AppProps) {
</Head> </Head>
<Component {...pageProps} /> <Component {...pageProps} />
</>; </>;
} }

View File

@@ -1,6 +1,6 @@
import {Html, Head, Main, NextScript} from "next/document"; import { Html, Head, Main, NextScript } from "next/document";
export default function Document() { export default function Document(): JSX.Element {
return ( return (
<Html lang="en"> <Html lang="en">
<Head/> <Head/>
@@ -10,4 +10,4 @@ export default function Document() {
</body> </body>
</Html> </Html>
); );
} }

View File

View File

@@ -1,6 +1,6 @@
import MainPage from "@/src/portfolio/MainPage"; import MainPage from "@/src/portfolio/MainPage";
export default function Home() { export default function Home(): JSX.Element {
return ( return (
<main> <main>
<MainPage/> <MainPage/>

View File

@@ -1,15 +0,0 @@
import { Work_Sans } from "next/font/google";
const workSans = Work_Sans({ subsets: ["latin"] });
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={workSans.className}>{children}</body>
</html>
);
}

View File

@@ -1,5 +0,0 @@
export default function Home() {
return (
<h1>:/</h1>
);
}

3744
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ import Achievements from "@/src/portfolio/sections/Achievements";
import Footer from "@/src/portfolio/sections/Footer"; import Footer from "@/src/portfolio/sections/Footer";
import SkillsAndLinks from "@/src/portfolio/sections/SkillsAndLinks"; import SkillsAndLinks from "@/src/portfolio/sections/SkillsAndLinks";
const MainPage = () => { const MainPage = (): JSX.Element => {
return ( return (
<div> <div>
<Intro/> <Intro/>
@@ -18,4 +18,4 @@ const MainPage = () => {
); );
}; };
export default MainPage; export default MainPage;

View File

@@ -1,4 +1,4 @@
import {AwardArgs} from "@/src/portfolio/helpers/Certificate"; import { AwardArgs } from "@/src/portfolio/helpers/Certificate";
const certificateData : AwardArgs[] = [ const certificateData : AwardArgs[] = [
{ {
@@ -16,21 +16,21 @@ const certificateData : AwardArgs[] = [
city: "Warsaw", city: "Warsaw",
awardDate: "July 2020", awardDate: "July 2020",
notes: [ notes: [
"Overall: 7.5 / 9.0, equivalent to C1", "Overall: 7.5 / 9.0, equivalent to C1"
] ]
}, },
{ {
title: "Project Management Fundamentals", title: "Project Management Fundamentals",
institution: "Project Management Institute", institution: "Project Management Institute",
city: "Warsaw", city: "Warsaw",
awardDate: "April 2019", awardDate: "April 2019"
}, },
{ {
title: "Project Management Principles", title: "Project Management Principles",
institution: "Project Management Institute", institution: "Project Management Institute",
city: "Warsaw", city: "Warsaw",
awardDate: "April 2019", awardDate: "April 2019"
} }
]; ];
export default certificateData; export default certificateData;

View File

@@ -1,4 +1,4 @@
import {EducationArgs} from "@/src/portfolio/helpers/Education"; import { EducationArgs } from "@/src/portfolio/helpers/Education";
const educationData : EducationArgs[] = [ const educationData : EducationArgs[] = [
{ {
@@ -39,8 +39,8 @@ const educationData : EducationArgs[] = [
"Final exam in Physics: 97% percentile", "Final exam in Physics: 97% percentile",
"Final exam in Mathematics: 92% percentile" "Final exam in Mathematics: 92% percentile"
], ],
useWith: true, //todo fix this (i.e. this does nothing) useWith: true //todo fix this (i.e. this does nothing)
} }
]; ];
export default educationData; export default educationData;

View File

@@ -63,4 +63,4 @@ export const modulesTaken : {
level: "UG", level: "UG",
score: 86 score: 86
} }
]; ];

View File

@@ -1,6 +1,6 @@
import {ProjectArguments, SkillEnum} from "@/src/portfolio/helpers/Project"; import { IProjectArguments, SkillEnum } from "@/src/portfolio/helpers/Project";
const projectData : ProjectArguments[] = [ const projectData : IProjectArguments[] = [
{ {
imagePath: "expertAgents.png", imagePath: "expertAgents.png",
tech: [ tech: [
@@ -55,7 +55,7 @@ const projectData : ProjectArguments[] = [
title: "Natural Computing: Implementing and analysis of PSO, GA and GP", title: "Natural Computing: Implementing and analysis of PSO, GA and GP",
tech: [ tech: [
SkillEnum.python, SkillEnum.python,
SkillEnum.numpy, SkillEnum.numpy
], ],
text: "During my Master's program, I had the opportunity to take a course on Natural Computing, where I implemented and analyzed three major algorithms: Particle Swarm Optimization (PSO), Genetic Algorithms (GA), and Genetic Programming (GP). This coursework allowed me to gain hands-on experience with these powerful optimization techniques, which are inspired by natural phenomena such as swarm intelligence and evolution. Through this project, I developed a deep understanding of the underlying principles of natural computing and its potential applications in various fields, such as engineering, finance, and biology. I am excited to showcase my implementation and analysis of PSO, GA, and GP on my website and demonstrate my proficiency in natural computing techniques.", text: "During my Master's program, I had the opportunity to take a course on Natural Computing, where I implemented and analyzed three major algorithms: Particle Swarm Optimization (PSO), Genetic Algorithms (GA), and Genetic Programming (GP). This coursework allowed me to gain hands-on experience with these powerful optimization techniques, which are inspired by natural phenomena such as swarm intelligence and evolution. Through this project, I developed a deep understanding of the underlying principles of natural computing and its potential applications in various fields, such as engineering, finance, and biology. I am excited to showcase my implementation and analysis of PSO, GA, and GP on my website and demonstrate my proficiency in natural computing techniques.",
github: "https://github.com/KuchtaVR6/nat_coursework", github: "https://github.com/KuchtaVR6/nat_coursework",
@@ -70,7 +70,7 @@ const projectData : ProjectArguments[] = [
SkillEnum.react, SkillEnum.react,
SkillEnum.html, SkillEnum.html,
SkillEnum.css, SkillEnum.css,
SkillEnum.express, SkillEnum.express
], ],
github: "https://github.com/KuchtaVR6/Learnopedia" github: "https://github.com/KuchtaVR6/Learnopedia"
}, },
@@ -148,7 +148,13 @@ const projectData : ProjectArguments[] = [
// { // {
// imagePath: "port1.png", // imagePath: "port1.png",
// title: "My previous portfolio website", // title: "My previous portfolio website",
// text: "This website was created as a challenge to myself to create an eye-pleasing and portable website with limited time. I decided to make it purely using HTML and CSS, and for the portability, I have used Bootstrap CSS. The resulting product is an informative, simple and good looking portfolio, which I was quite happy with. Throughout this academic year, I have gained more confidence in using React, I have decided to remake my portfolio this time with a more interesting and responsive design in mind, whilst maintaining the readability of the older version.", // text: "This website was created as a challenge to myself to create an eye-pleasing " +
// "and portable website with limited time. I decided to make it purely using HTML and CSS, " +
// "and for the portability, I have used Bootstrap CSS. The resulting product is an " +
// "informative, simple and good looking portfolio, which I was quite happy with. " +
// "Throughout this academic year, I have gained more confidence in using React, I have " +
// "decided to remake my portfolio this time with a more interesting and responsive design " +
// "in mind, whilst maintaining the readability of the older version.",
// tech: [ // tech: [
// SkillEnum.html, // SkillEnum.html,
// SkillEnum.css, // SkillEnum.css,
@@ -177,11 +183,17 @@ const projectData : ProjectArguments[] = [
SkillEnum.linux, SkillEnum.linux,
SkillEnum.design3d SkillEnum.design3d
] ]
}, }
// { // {
// imagePath: "port3.png", // imagePath: "port3.png",
// title: "My current portfolio website", // title: "My current portfolio website",
// text: "And finally, this website is my most recent project. Design-wise I wanted to keep the website minimalistic but stunning at the same time to show my skills, and I have kept accessibility in mind. I had created this project with plentiful react to experience and I created this website with a very high standard of code and with reusability in mind so that I don't have to rewrite this website in the future. Admittedly I will probably end up doing it anyway because I love coding and improving my websites. ", // text: "And finally, this website is my most recent project. Design-wise I wanted to " +
// "keep the website minimalistic but stunning at the same time to show my skills, and I " +
// "have kept accessibility in mind. I had created this project with plentiful react to " +
// "experience and I created this website with a very high standard of code and with " +
// "reusability in mind so that I don't have to rewrite this website in the future. " +
// "Admittedly I will probably end up doing it anyway because I love coding and " +
// "improving my websites. ",
// tech: [ // tech: [
// SkillEnum.react, // SkillEnum.react,
// SkillEnum.html, // SkillEnum.html,
@@ -193,4 +205,4 @@ const projectData : ProjectArguments[] = [
// } // }
]; ];
export default projectData; export default projectData;

View File

@@ -1,5 +1,5 @@
import {SkillEnum} from "@/src/portfolio/helpers/Project"; import { SkillEnum } from "@/src/portfolio/helpers/Project";
import {ProficiencyLevel} from "@/src/portfolio/helpers/SkillDisplay"; import { ProficiencyLevel } from "@/src/portfolio/helpers/SkillDisplay";
export const skillsInCategories = { export const skillsInCategories = {
"Programming Languages": [ "Programming Languages": [

View File

@@ -1,4 +1,4 @@
import {WorkExperienceArgs} from "@/src/portfolio/helpers/WorkExperience"; import { WorkExperienceArgs } from "@/src/portfolio/helpers/WorkExperience";
const workExperienceData : WorkExperienceArgs[] = [ const workExperienceData : WorkExperienceArgs[] = [
{ {
@@ -97,7 +97,7 @@ const workExperienceData : WorkExperienceArgs[] = [
country: "Poland", country: "Poland",
startDate: "July 2018", startDate: "July 2018",
endDate: "August 2018" endDate: "August 2018"
}, }
]; ];
export const workExperienceParagraph = export const workExperienceParagraph =
@@ -114,4 +114,4 @@ export const workExperienceParagraph =
"working on several AI projects, applying my theoretical machine learning knowledge to real-world problems. " + "working on several AI projects, applying my theoretical machine learning knowledge to real-world problems. " +
"Across these diverse roles, I have developed a strong work ethic and a broad set of transferable skills."; "Across these diverse roles, I have developed a strong work ethic and a broad set of transferable skills.";
export default workExperienceData; export default workExperienceData;

View File

@@ -1,15 +1,15 @@
import React, {FC, useState} from "react"; import React, { FC, useState } from "react";
import { CSSTransition } from "react-transition-group"; import { CSSTransition } from "react-transition-group";
type args = { type AccordionArgs = {
title : string, title : string,
children : React.ReactNode, children : React.ReactNode,
} }
const Accordion : FC<args> = ({ title, children }) => { const Accordion : FC<AccordionArgs> = ({ title, children }): JSX.Element => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const toggleAccordion = () => { const toggleAccordion = (): void => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
}; };

View File

@@ -1,4 +1,4 @@
import {FC} from "react"; import { FC } from "react";
import styles from "../styling/achievements.module.scss"; import styles from "../styling/achievements.module.scss";
export type AwardArgs = { export type AwardArgs = {
@@ -11,7 +11,7 @@ export type AwardArgs = {
const Award : FC<AwardArgs> = (props) => { const Award : FC<AwardArgs> = (props) => {
const getCountryEmoji = () => { const getCountryEmoji = (): string => {
switch (props.city) { switch (props.city) {
case "Warsaw": case "Warsaw":
return ", PL"; return ", PL";
@@ -48,4 +48,4 @@ const Award : FC<AwardArgs> = (props) => {
); );
}; };
export default Award; export default Award;

View File

@@ -1,4 +1,4 @@
import {FC} from "react"; import { FC } from "react";
import styles from "../styling/achievements.module.scss"; import styles from "../styling/achievements.module.scss";
export type EducationArgs = { export type EducationArgs = {
@@ -14,7 +14,7 @@ export type EducationArgs = {
const Education : FC<EducationArgs> = (props) => { const Education : FC<EducationArgs> = (props) => {
const getCountryEmoji = () => { const getCountryEmoji = (): string => {
switch (props.city) { switch (props.city) {
case "Warsaw": case "Warsaw":
return ", Poland"; return ", Poland";
@@ -26,7 +26,7 @@ const Education : FC<EducationArgs> = (props) => {
}; };
return ( return (
<div style={{fontSize: "1.1em"}} className={styles.education} data-aos={"fade-right"}> <div style={{ fontSize: "1.1em" }} className={styles.education} data-aos={"fade-right"}>
<div> <div>
<span className={styles.title}>{props.title}</span> in <b>{props.subtitle}</b> <span className={styles.title}>{props.title}</span> in <b>{props.subtitle}</b>
<br/> <br/>
@@ -48,10 +48,10 @@ const Education : FC<EducationArgs> = (props) => {
{props.endDate ? {props.endDate ?
<> <>
<i>From:&nbsp;&nbsp;</i> <i>From:&nbsp;&nbsp;</i>
<span style={{float:"right"}}>{props.startDate}</span> <span style={{ float:"right" }}>{props.startDate}</span>
<br/> <br/>
<i>To:&nbsp;&nbsp;</i> <i>To:&nbsp;&nbsp;</i>
<span style={{float:"right"}}>{props.endDate}</span> <span style={{ float:"right" }}>{props.endDate}</span>
</>: </>:
"Since " + props.startDate "Since " + props.startDate
} }
@@ -60,4 +60,4 @@ const Education : FC<EducationArgs> = (props) => {
); );
}; };
export default Education; export default Education;

View File

@@ -1,28 +1,27 @@
import {FC, MutableRefObject, useEffect, useRef, useState} from "react"; import { FC, MutableRefObject, useEffect, useRef, useState } from "react";
type ProgressBarArgs = {
type args = {
percentage : number, percentage : number,
customScore? : string, customScore? : string,
} }
const ProgressBar : FC<args> = ({percentage,customScore}) => { const ProgressBar : FC<ProgressBarArgs> = ({ percentage,customScore }): JSX.Element => {
const ref = useRef<HTMLDivElement>() as MutableRefObject<HTMLDivElement>; const ref = useRef<HTMLDivElement>() as MutableRefObject<HTMLDivElement>;
const onScreen = useOnScreen(ref); const onScreen = useOnScreen(ref);
const [current,setCurrent] = useState(0); const [current,setCurrent] = useState(0);
useEffect(()=>{ useEffect(() => {
if(onScreen && current==0) if (onScreen && current==0)
{ {
let counter = 0; let counter = 0;
const id = setInterval(() => { const id = setInterval(() => {
if(counter<percentage*2) if (counter<percentage*2)
{ {
counter += (percentage/100); counter += (percentage/100);
setCurrent(counter); setCurrent(counter);
} }
else{ else {
if(counter>percentage*2) if (counter>percentage*2)
{ {
setCurrent(percentage*2); setCurrent(percentage*2);
} }
@@ -30,20 +29,20 @@ const ProgressBar : FC<args> = ({percentage,customScore}) => {
} }
},25); },25);
} }
},[onScreen]); },[onScreen, current, percentage]);
return ( return (
<div className={"pBContainer"} ref={ref}> <div className={"pBContainer"} ref={ref}>
<div className={"progressBar"}> <div className={"progressBar"}>
<div className={"inner"} style={{width : `${current/2}%`}}></div> <div className={"inner"} style={{ width : `${current/2}%` }}></div>
</div> </div>
<div className={"below"} style={{width : customScore? "100%" : `${current/2}%`, textAlign: "right"}}>{customScore? customScore : Math.ceil(current/2)+"%"}</div> <div className={"below"} style={{ width : customScore? "100%" : `${current/2}%`, textAlign: "right" }}>{customScore? customScore : Math.ceil(current/2)+"%"}</div>
</div> </div>
); );
}; };
function useOnScreen(ref : MutableRefObject<HTMLDivElement>, rootMargin = "0px") { function useOnScreen(ref : MutableRefObject<HTMLDivElement>, rootMargin = "0px"): boolean {
// State and setter for storing whether element is visible // State and setter for storing whether element is visible
const [isIntersecting, setIntersecting] = useState(false); const [isIntersecting, setIntersecting] = useState(false);
useEffect(() => { useEffect(() => {
@@ -53,22 +52,25 @@ function useOnScreen(ref : MutableRefObject<HTMLDivElement>, rootMargin = "0px")
setIntersecting(entry.isIntersecting); setIntersecting(entry.isIntersecting);
}, },
{ {
rootMargin, rootMargin
} }
); );
if (ref.current) { const currentRef = ref.current;
observer.observe(ref.current); if (currentRef) {
observer.observe(currentRef);
} }
return () => { return () => {
try{ if (currentRef) {
observer.unobserve(ref.current); try {
} observer.unobserve(currentRef);
catch (e) { }
console.log("REPORTED: observer failure :/"); catch {
// Observer failure handled silently
}
} }
}; };
}, []); // Empty array ensures that effect is only run on mount and unmount }, [ref, rootMargin]);
return isIntersecting; return isIntersecting;
} }
export default ProgressBar; export default ProgressBar;

View File

@@ -1,9 +1,9 @@
import Image from "next/image"; import Image from "next/image";
import styles from "../styling/projects.module.scss"; import styles from "../styling/projects.module.scss";
import {VscGithub} from "react-icons/vsc"; import { VscGithub } from "react-icons/vsc";
import {BsGlobe2, BsFileEarmarkMedical} from "react-icons/bs"; import { BsGlobe2, BsFileEarmarkMedical } from "react-icons/bs";
export interface ProjectArguments { export interface IProjectArguments {
imagePath : string, imagePath : string,
title : string, title : string,
text : string, text : string,
@@ -47,7 +47,7 @@ export enum SkillEnum {
english = "English", english = "English",
} }
const Project = ({imagePath, title, text, github, access, document} : ProjectArguments) => { const Project = ({ imagePath, title, text, github, access, document } : IProjectArguments): JSX.Element => {
return ( return (
<div className={styles.projectDisplay}> <div className={styles.projectDisplay}>
<div className={styles.text}> <div className={styles.text}>
@@ -92,4 +92,4 @@ const Project = ({imagePath, title, text, github, access, document} : ProjectArg
); );
}; };
export default Project; export default Project;

View File

@@ -1,8 +1,8 @@
import {FC} from "react"; import { FC } from "react";
import styles from "../styling/projects.module.scss"; import styles from "../styling/projects.module.scss";
import {SkillEnum} from "@/src/portfolio/helpers/Project"; import { SkillEnum } from "@/src/portfolio/helpers/Project";
type args = { type SkillDisplayArgs = {
name : SkillEnum, name : SkillEnum,
level? : ProficiencyLevel, level? : ProficiencyLevel,
} }
@@ -15,8 +15,7 @@ export enum ProficiencyLevel {
master = "Master", master = "Master",
} }
const SkillDisplay : FC<SkillDisplayArgs> = ({ name, level }): JSX.Element => {
const SkillDisplay : FC<args> = ({name, level}) => {
const level_to_emoji = { const level_to_emoji = {
[ProficiencyLevel.beginner]: "🌱", [ProficiencyLevel.beginner]: "🌱",
[ProficiencyLevel.intermediate]: "🌳", [ProficiencyLevel.intermediate]: "🌳",
@@ -29,7 +28,7 @@ const SkillDisplay : FC<args> = ({name, level}) => {
return ( return (
<div id={name.toLowerCase()} className={styles.technology}> <div id={name.toLowerCase()} className={styles.technology}>
<b>{name}&nbsp;&nbsp;</b> <b>{name}&nbsp;&nbsp;</b>
<span style={{float: "right", fontSize: "0.8em"}}>{level} {level_to_emoji[level]}</span> <span style={{ float: "right", fontSize: "0.8em" }}>{level} {level_to_emoji[level]}</span>
</div> </div>
); );
} }
@@ -40,4 +39,4 @@ const SkillDisplay : FC<args> = ({name, level}) => {
); );
}; };
export default SkillDisplay; export default SkillDisplay;

View File

@@ -1,4 +1,4 @@
import {FC} from "react"; import { FC } from "react";
import styles from "../styling/experience.module.scss"; import styles from "../styling/experience.module.scss";
export type WorkExperienceArgs = { export type WorkExperienceArgs = {
@@ -13,7 +13,7 @@ export type WorkExperienceArgs = {
const WorkExperience : FC<WorkExperienceArgs> = (props) => { const WorkExperience : FC<WorkExperienceArgs> = (props) => {
const getEmojiForIndustry = () => { const getEmojiForIndustry = (): string | undefined => {
switch (props.industry) { switch (props.industry) {
case "Education": case "Education":
return "🎓"; return "🎓";
@@ -43,4 +43,4 @@ const WorkExperience : FC<WorkExperienceArgs> = (props) => {
); );
}; };
export default WorkExperience; export default WorkExperience;

View File

@@ -4,7 +4,7 @@ import styles from "../styling/achievements.module.scss";
import educationData from "@/src/portfolio/data/educationData"; import educationData from "@/src/portfolio/data/educationData";
import certificateData from "@/src/portfolio/data/certificateData"; import certificateData from "@/src/portfolio/data/certificateData";
const Achievements = () => { const Achievements = (): JSX.Element => {
return ( return (
<div className={styles.section} id={"achievements"}> <div className={styles.section} id={"achievements"}>
<div> <div>
@@ -27,4 +27,4 @@ const Achievements = () => {
); );
}; };
export default Achievements; export default Achievements;

View File

@@ -1,11 +1,11 @@
import {Splide, SplideSlide} from "@splidejs/react-splide"; import { Splide, SplideSlide } from "@splidejs/react-splide";
import {useEffect, useState} from "react"; import { useEffect, useState } from "react";
import styles from "../styling/experience.module.scss"; import styles from "../styling/experience.module.scss";
import WorkExperience from "@/src/portfolio/helpers/WorkExperience"; import WorkExperience from "@/src/portfolio/helpers/WorkExperience";
import workExperienceData, {workExperienceParagraph} from "@/src/portfolio/data/workExperienceData"; import workExperienceData, { workExperienceParagraph } from "@/src/portfolio/data/workExperienceData";
const Experience = () => { const Experience = (): JSX.Element => {
const calcPagesOnWidth = (width : number) => { const calcPagesOnWidth = (width : number): number => {
return Math.floor(width / 800 + 1); return Math.floor(width / 800 + 1);
}; };
@@ -14,7 +14,7 @@ const Experience = () => {
useEffect(() => { useEffect(() => {
setPages(calcPagesOnWidth(window.innerWidth)); setPages(calcPagesOnWidth(window.innerWidth));
const handleResize = () => { const handleResize = (): void => {
setPages(calcPagesOnWidth(window.innerWidth)); setPages(calcPagesOnWidth(window.innerWidth));
}; };
@@ -53,4 +53,4 @@ const Experience = () => {
); );
}; };
export default Experience; export default Experience;

View File

@@ -1,6 +1,6 @@
import styles from "../styling/footer.module.scss"; import styles from "../styling/footer.module.scss";
const Footer = () => { const Footer = (): JSX.Element => {
return ( return (
<footer className={styles.section}> <footer className={styles.section}>
Copyright &copy; Patryk Kuchta {new Date().getFullYear()} Copyright &copy; Patryk Kuchta {new Date().getFullYear()}
@@ -8,4 +8,4 @@ const Footer = () => {
); );
}; };
export default Footer; export default Footer;

View File

@@ -1,65 +0,0 @@
import styles from "../styling/intro.module.scss";
import Image from "next/image";
import profilePic from "../../../public/portfolio/profile.png";
import logo from "../../../public/portfolio/logo.svg";
const Intro = () => {
const greeting = () => {
const hour = new Date().getHours();
if (hour > 4 && hour < 12) {
return "Good Morning!";
} else if (hour < 18) {
return "Good Afternoon!";
} else {
return "Good Evening!";
}
};
return (
<section className={styles.section}>
<header className={styles.topBar} data-aos={"zoom-in-down"} data-aos-duration={"500"}>
<div className={styles.row}>
<div className={styles.logoContainer}>
<Image src={logo} alt={"Kuchta logo"} fill={true}/>
</div>
<a href={"#achievements"}>Achievements</a>
<a href={"#experience"}>Experience</a>
<a href={"#projects"}>Projects</a>
<a href={"#skills"}>Skills</a>
</div>
<div className={styles.left}>
<a href={"mailto: patrick@kuchta.uk"}>patrick@kuchta.uk</a>
<span>/</span>
<a href={"mailto: patryk@kuchta.uk"}>patryk@kuchta.uk</a>
</div>
</header>
<div className={styles.mainContent}>
<div className={styles.text} data-aos={"fade-in"} data-aos-duration={"500"}>
<div className={styles.larger}>
<span className={styles.greeting}>
{greeting()}
</span>
<br/>
I am
<b> Patryk Kuchta, </b>
an aspiring
<br/>
<b>Artificial Intelligence Scientist</b>.
</div>
<span data-aos={"fade-in"} data-aos-offset={"200"}><i>and yes, the greeting is synced to your time.</i></span>
</div>
<div className={styles.profileContainer}>
<Image
src={profilePic}
alt={"Frontal image showing Patryk Kuchta"}
fill={true}
/>
</div>
</div>
</section>
);
};
export default Intro;

View File

@@ -0,0 +1,17 @@
import styles from "../../styling/intro.module.scss";
import TopBar from "./TopBar";
import IntroContent from "./IntroContent";
import { getGreeting } from "./helpers";
const Intro = (): JSX.Element => {
const greeting = getGreeting();
return (
<section className={styles.section}>
<TopBar/>
<IntroContent greeting={greeting}/>
</section>
);
};
export default Intro;

View File

@@ -0,0 +1,39 @@
import Image from "next/image";
import styles from "../../styling/intro.module.scss";
import profilePic from "../../../../public/portfolio/profile.png";
interface IIntroContentProps {
greeting: string;
}
const IntroContent = ({ greeting }: IIntroContentProps): JSX.Element => {
return (
<div className={styles.mainContent}>
<div className={styles.text} data-aos={"fade-in"} data-aos-duration={"500"}>
<div className={styles.larger}>
<span className={styles.greeting}>
{greeting}
</span>
<br/>
I am
<b> Patryk Kuchta, </b>
an aspiring
<br/>
<b>Artificial Intelligence Scientist</b>.
</div>
<span data-aos={"fade-in"} data-aos-offset={"200"}>
<i>and yes, the greeting is synced to your time.</i>
</span>
</div>
<div className={styles.profileContainer}>
<Image
src={profilePic}
alt={"Frontal image showing Patryk Kuchta"}
fill={true}
/>
</div>
</div>
);
};
export default IntroContent;

View File

@@ -0,0 +1,26 @@
import Image from "next/image";
import styles from "../../styling/intro.module.scss";
import logo from "../../../../public/portfolio/logo.svg";
const TopBar = (): JSX.Element => {
return (
<header className={styles.topBar} data-aos={"zoom-in-down"} data-aos-duration={"500"}>
<div className={styles.row}>
<div className={styles.logoContainer}>
<Image src={logo} alt={"Kuchta logo"} fill={true}/>
</div>
<a href={"#achievements"}>Achievements</a>
<a href={"#experience"}>Experience</a>
<a href={"#projects"}>Projects</a>
<a href={"#skills"}>Skills</a>
</div>
<div className={styles.left}>
<a href={"mailto: patrick@kuchta.uk"}>patrick@kuchta.uk</a>
<span>/</span>
<a href={"mailto: patryk@kuchta.uk"}>patryk@kuchta.uk</a>
</div>
</header>
);
};
export default TopBar;

View File

@@ -0,0 +1,10 @@
export const getGreeting = (): string => {
const hour = new Date().getHours();
if (hour > 4 && hour < 12) {
return "Good Morning!";
} else if (hour < 18) {
return "Good Afternoon!";
} else {
return "Good Evening!";
}
};

View File

@@ -0,0 +1,3 @@
import Intro from "./Intro";
export default Intro;

View File

@@ -1,16 +1,16 @@
import {Splide, SplideSlide} from "@splidejs/react-splide"; import { Splide, SplideSlide } from "@splidejs/react-splide";
import Project from "@/src/portfolio/helpers/Project"; import Project from "@/src/portfolio/helpers/Project";
import projectData from "@/src/portfolio/data/projectData"; import projectData from "@/src/portfolio/data/projectData";
import styles from "../styling/projects.module.scss"; import styles from "../styling/projects.module.scss";
const Projects = () => { const Projects = (): JSX.Element => {
return ( return (
<div className={styles.section} id={"projects"}> <div className={styles.section} id={"projects"}>
<Splide <Splide
options={{ options={{
rewind: true, rewind: true,
type: "slide", type: "slide",
perPage: 1, perPage: 1
}} }}
> >
{ projectData.map((entry, key) => { { projectData.map((entry, key) => {
@@ -24,4 +24,4 @@ const Projects = () => {
); );
}; };
export default Projects; export default Projects;

View File

@@ -1,107 +0,0 @@
import styles from "../styling/skillsLinks.module.scss";
import SkillDisplay from "@/src/portfolio/helpers/SkillDisplay";
import Accordion from "@/src/portfolio/helpers/Accordion";
import {skillsInCategories} from "@/src/portfolio/data/skillsData";
import {modulesTaken} from "@/src/portfolio/data/modulesTaken";
const SkillsAndLinks = () => {
const moreInfoLinks = [
{
title: "Github",
link: "https://github.com/KuchtaVR6/"
},
{
title: "LinkedIn",
link: "https://linkedin.com/in/kuchtap"
},
{
title: "Curriculum Vitae",
link: "/PatrykKuchta_CV.pdf"
}
];
const contactLinks = [
{
title: "Email",
link: "mailto:patryk@kuchta.uk"
},
{
title: "LinkedIn",
link: "https://linkedin.com/in/kuchtap"
}
];
return (
<div className={styles.section} id={"skills"}>
<div data-aos = {"fade-left"} className={styles.skills}>
<div>
<i>Press the &quot;+&quot; to expand a section</i>&nbsp;👀
</div>
<div className={styles.innerskills}>
{
Object.entries(skillsInCategories).map(([category, skills]) => {
console.log(skills);
return <Accordion title={category} key={category}>
{
skills.map((skill) => {
return <SkillDisplay {...skill} key={skill.name} />;
})
}
</Accordion>;
})
}
<Accordion title={"Modules Taken"}>
{
modulesTaken.map(({name, level, score}) => {
return <div id={name.toLowerCase()}
className={styles.technology}
key={name}>
<b>{name}&nbsp;&nbsp;</b>
<span style={{float: "right", fontSize: "0.8em"}}>
{score>=0? score + "%" : "🔮"} <i>{level}</i>
</span>
</div>;
})
}
</Accordion>
</div>
</div>
<div data-aos = {"fade-right"} className={styles.otherLinks}>
<h2>Find out more about me:</h2>
<ul>
{
moreInfoLinks.map(({title, link}, key) => {
return (
<li key = {key}>
<a href={link}>
{title}
</a>
</li>
);
}
)
}
</ul>
<h2>Contact me through:</h2>
<ul>
{
contactLinks.map(({title, link}, key) => {
return (
<li key = {key}>
<a href={link}>
{title}
</a>
</li>
);
}
)
}
</ul>
</div>
</div>
);
};
export default SkillsAndLinks;

View File

@@ -0,0 +1,71 @@
import styles from "../../styling/skillsLinks.module.scss";
interface ILink {
title: string;
link: string;
}
const moreInfoLinks: ILink[] = [
{
title: "Github",
link: "https://github.com/KuchtaVR6/"
},
{
title: "LinkedIn",
link: "https://linkedin.com/in/kuchtap"
},
{
title: "Curriculum Vitae",
link: "/PatrykKuchta_CV.pdf"
}
];
const contactLinks: ILink[] = [
{
title: "Email",
link: "mailto:patryk@kuchta.uk"
},
{
title: "LinkedIn",
link: "https://linkedin.com/in/kuchtap"
}
];
const LinksSection = (): JSX.Element => {
return (
<div data-aos = {"fade-right"} className={styles.otherLinks}>
<h2>Find out more about me:</h2>
<ul>
{
moreInfoLinks.map(({ title, link }, key) => {
return (
<li key = {key}>
<a href={link}>
{title}
</a>
</li>
);
}
)
}
</ul>
<h2>Contact me through:</h2>
<ul>
{
contactLinks.map(({ title, link }, key) => {
return (
<li key = {key}>
<a href={link}>
{title}
</a>
</li>
);
}
)
}
</ul>
</div>
);
};
export default LinksSection;

View File

@@ -0,0 +1,14 @@
import styles from "../../styling/skillsLinks.module.scss";
import SkillsSection from "./SkillsSection";
import LinksSection from "./LinksSection";
const SkillsAndLinks = (): JSX.Element => {
return (
<div className={styles.section} id={"skills"}>
<SkillsSection/>
<LinksSection/>
</div>
);
};
export default SkillsAndLinks;

View File

@@ -0,0 +1,45 @@
import styles from "../../styling/skillsLinks.module.scss";
import SkillDisplay from "@/src/portfolio/helpers/SkillDisplay";
import Accordion from "@/src/portfolio/helpers/Accordion";
import { skillsInCategories } from "@/src/portfolio/data/skillsData";
import { modulesTaken } from "@/src/portfolio/data/modulesTaken";
const SkillsSection = (): JSX.Element => {
return (
<div data-aos = {"fade-left"} className={styles.skills}>
<div>
<i>Press the &quot;+&quot; to expand a section</i>&nbsp;👀
</div>
<div className={styles.innerskills}>
{
Object.entries(skillsInCategories).map(([category, skills]) => {
return <Accordion title={category} key={category}>
{
skills.map((skill) => {
return <SkillDisplay {...skill} key={skill.name} />;
})
}
</Accordion>;
})
}
<Accordion title={"Modules Taken"}>
{
modulesTaken.map(({ name, level, score }) => {
return <div id={name.toLowerCase()}
className={styles.technology}
key={name}>
<b>{name}&nbsp;&nbsp;</b>
<span style={{ float: "right", fontSize: "0.8em" }}>
{score>=0? score + "%" : "🔮"} <i>{level}</i>
</span>
</div>;
})
}
</Accordion>
</div>
</div>
);
};
export default SkillsSection;

View File

@@ -0,0 +1,3 @@
import SkillsAndLinks from "./SkillsAndLinks";
export default SkillsAndLinks;

29
src/types/images.d.ts vendored Normal file
View File

@@ -0,0 +1,29 @@
declare module "*.png" {
const value: string;
export default value;
}
declare module "*.jpg" {
const value: string;
export default value;
}
declare module "*.jpeg" {
const value: string;
export default value;
}
declare module "*.svg" {
const value: string;
export default value;
}
declare module "*.gif" {
const value: string;
export default value;
}
declare module "*.webp" {
const value: string;
export default value;
}

4
src/types/scss.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
declare module "*.module.scss" {
const classes: { [key: string]: string };
export default classes;
}

View File

@@ -23,6 +23,6 @@
"@/*": ["./*"] "@/*": ["./*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "src/types/**/*.d.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

87
windsurf-commands.sh Executable file
View File

@@ -0,0 +1,87 @@
#!/bin/bash
# Windsurf Commands Script
# This script provides common development commands for the portfolio project
set -e
show_help() {
echo "Usage: ./windsurf-commands.sh [COMMAND]"
echo ""
echo "Available commands:"
echo " lint - Run ESLint to check for code issues"
echo " lintfix - Run ESLint with auto-fix enabled"
echo " typecheck - Run TypeScript type checking"
echo " security:audit - Run security vulnerability scan"
echo " security:check - Generate security report (JSON)"
echo " security:outdated - Check for outdated dependencies"
echo " help - Show this help message"
echo ""
}
run_lint() {
echo "🔍 Running ESLint..."
pnpm run lint
echo "✅ Linting complete!"
}
run_lintfix() {
echo "🔧 Running ESLint with auto-fix..."
pnpm run lintfix
echo "✅ Linting with auto-fix complete!"
}
run_typecheck() {
echo "🔎 Running TypeScript type check..."
pnpm run typecheck
echo "✅ Type checking complete!"
}
run_security_audit() {
echo "🔒 Running security audit..."
pnpm run security:audit
echo "✅ Security audit complete!"
}
run_security_check() {
echo "📋 Generating security report..."
pnpm run security:check
echo "✅ Security report generated: security-report.json"
}
run_security_outdated() {
echo "📦 Checking for outdated dependencies..."
pnpm run security:outdated
echo "✅ Outdated check complete!"
}
# Main script logic
case "${1:-help}" in
lint)
run_lint
;;
lintfix)
run_lintfix
;;
typecheck)
run_typecheck
;;
security:audit)
run_security_audit
;;
security:check)
run_security_check
;;
security:outdated)
run_security_outdated
;;
help|--help|-h)
show_help
;;
*)
echo "❌ Unknown command: $1"
echo ""
show_help
exit 1
;;
esac