refactor(v3): Initial revamp
parent
d85095095d
commit
e062b44e1c
@ -0,0 +1,8 @@
|
||||
**
|
||||
|
||||
!next.config.js
|
||||
!tsconfig.json
|
||||
!package.json
|
||||
!yarn.lock
|
||||
!public
|
||||
!src
|
@ -1,5 +1 @@
|
||||
VITE_GITHUB_USERNAME=Guusvanmeerveld
|
||||
VITE_TWITTER_USERNAME=Guusvanmeerveld
|
||||
VITE_KOFI_USERNAME=Guusvanmeerveld
|
||||
VITE_YOUTUBE_CHANNEL_ID=UCYuqpoMay5SezCBrA_HKVWQ
|
||||
VITE_MASTODON_URL=https://c.im/web/@guusvanmeerveld
|
||||
NEXT_PUBLIC_GITHUB_USERNAME=Guusvanmeerveld
|
@ -0,0 +1,67 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 8
|
||||
},
|
||||
"plugins": [
|
||||
"prettier",
|
||||
"css-modules"
|
||||
],
|
||||
"ignorePatterns": [
|
||||
"node_modules/*",
|
||||
".next/*",
|
||||
".out/*"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:css-modules/recommended"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:jsx-a11y/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"react/prop-types": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"jsx-a11y/anchor-is-valid": "off",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error"
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true,
|
||||
"allowConciseArrowFunctionExpressionsStartingWithVoid": true,
|
||||
"allowTypedFunctionExpressions": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
@ -0,0 +1,20 @@
|
||||
name: Auto-merge dependabot
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-merge:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
name: Checkout
|
||||
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
name: Auto merge
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
@ -0,0 +1,45 @@
|
||||
name: deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
typescript:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup NodeJS v12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install Dependencies
|
||||
run: yarn install
|
||||
- name: Check for syntax errors
|
||||
run: yarn test-build
|
||||
- name: ESlint check
|
||||
run: yarn lint
|
||||
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: typescript
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Build Dockerfile and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: guusvanmeerveld/portfolio:latest
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
@ -1,42 +0,0 @@
|
||||
name: "Deploy website"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- src/**
|
||||
- package.json
|
||||
- yarn.lock
|
||||
- tsconfig.*
|
||||
- vite.config.ts
|
||||
- .env
|
||||
- index.html
|
||||
- public
|
||||
|
||||
jobs:
|
||||
deploy_to_pages:
|
||||
name: Build pages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: "16.x"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install NPM dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: Build client
|
||||
run: yarn run build
|
||||
|
||||
- name: Deploy to pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./dist
|
||||
cname: guusvanmeerveld.dev
|
@ -1,24 +1,4 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.env.local
|
||||
.next
|
||||
yarn-error.log
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"printWidth": 80,
|
||||
"arrowParens": "always",
|
||||
"importOrderSeparation": true,
|
||||
"importOrder": [
|
||||
"@rollup/.*",
|
||||
"rollup-.*",
|
||||
"^..?/.*",
|
||||
"^use-local-storage-state$",
|
||||
"^styled-components$",
|
||||
"^react.*",
|
||||
"^preact$",
|
||||
"^preact.*",
|
||||
"^axios.*",
|
||||
"^@src/.*",
|
||||
"^@models/.*",
|
||||
"^@interfaces/.*",
|
||||
"^@styles/.*",
|
||||
"^@shared/.*",
|
||||
"^@utils/.*",
|
||||
"^@components/.*",
|
||||
"^@svg/.*"
|
||||
]
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"useTabs": true,
|
||||
"semi": true,
|
||||
"printWidth": 80,
|
||||
"arrowParens": "always"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-idiomatic-order"
|
||||
],
|
||||
"rules": {
|
||||
"indentation": "tab"
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"prettier.configPath": ".prettierrc"
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
FROM node:alpine AS deps
|
||||
|
||||
WORKDIR /app
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
FROM node:alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
ENV NEXT_TELEMETRY_DISABLED 1;
|
||||
RUN yarn build && yarn install --production --ignore-scripts --prefer-offline
|
||||
|
||||
FROM node:alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
RUN addgroup -g 1001 -S nodejs
|
||||
RUN adduser -S nextjs -u 1001
|
||||
|
||||
COPY --from=builder /app/next.config.js ./
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["yarn", "start"]
|
@ -1,5 +0,0 @@
|
||||
# Portfolio
|
||||
|
||||
This is my portfolio website to show off all of my projects and provide my socials.
|
||||
|
||||
It's build using Preact and Styled-Components and uses Vite as its build tool.
|
@ -0,0 +1,8 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
container_name: app
|
||||
ports:
|
||||
- 3000:3000
|
@ -1,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Guus' Portfolio</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #000000;
|
||||
color: #eee;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<noscript>Javascript needs to be enabled to run this application</noscript>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
@ -0,0 +1,7 @@
|
||||
// @ts-check
|
||||
/**
|
||||
* @type {import('next/dist/next-server/server/config').NextConfig}
|
||||
**/
|
||||
module.exports = {
|
||||
reactStrictMode: true,
|
||||
}
|
@ -1,39 +1,42 @@
|
||||
{
|
||||
"name": "portfolio",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"format": "prettier src --write",
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "next build && next export",
|
||||
"test-build": "tsc",
|
||||
"lint": "eslint src",
|
||||
"stylelint": "npx stylelint **/*.scss",
|
||||
"full-test": "yarn test-build && yarn lint && yarn stylelint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/montserrat": "^4.5.11",
|
||||
"@fontsource/prompt": "^4.5.8",
|
||||
"@tanstack/react-query": "^4.0.10",
|
||||
"@tanstack/react-query-devtools": "^4.0.10",
|
||||
"axios": "^0.27.2",
|
||||
"bootstrap": "^5.2.0",
|
||||
"preact": "^10.9.0",
|
||||
"react-bootstrap": "^2.4.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-router-dom": "6",
|
||||
"react-spring": "^9.5.2",
|
||||
"reset-css": "^5.0.1",
|
||||
"styled-components": "^5.3.5",
|
||||
"use-local-storage-state": "^18.1.0",
|
||||
"vite-plugin-imagemin": "^0.6.1"
|
||||
"axios": "^1.1.2",
|
||||
"configcat-node": "^8.0.0",
|
||||
"next": "^12.1.0",
|
||||
"next-seo": "^4.24.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"spectre.css": "^0.5.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.3.0",
|
||||
"@rollup/plugin-alias": "^3.1.9",
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.3.0",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.6.4",
|
||||
"vite": "^3.0.0",
|
||||
"vite-tsconfig-paths": "^3.5.0"
|
||||
"@types/node": "^15.12.1",
|
||||
"@types/react": "^17.0.9",
|
||||
"@types/react-dom": "^17.0.6",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||
"@typescript-eslint/parser": "^4.26.0",
|
||||
"eslint": "^7.28.0",
|
||||
"eslint-plugin-css-modules": "^2.11.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"prettier": "^2.3.1",
|
||||
"sass": "^1.34.1",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-idiomatic-order": "^8.1.0",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"typescript": "^4.3.2"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 89 KiB |
@ -0,0 +1,40 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { FC } from "react";
|
||||
|
||||
import { BestRepository } from "@interfaces/repository";
|
||||
|
||||
const BestRepository: FC<{ repository: BestRepository }> = ({ repository }) => {
|
||||
return (
|
||||
<div className="hero bg-primary">
|
||||
<div className="container">
|
||||
<div className="columns">
|
||||
<div className="column col-8 col-mx-auto">
|
||||
<h3 className="text-secondary">My most popular project:</h3>
|
||||
<h1>{repository.name}</h1>
|
||||
<h3 className="text-secondary">
|
||||
{repository.stargazers_count} Star(s)
|
||||
</h3>
|
||||
<h5>{repository.description}</h5>
|
||||
<p className="text-secondary">
|
||||
Written in {repository.language}, has{" "}
|
||||
{repository.open_issues_count} issue(s) and{" "}
|
||||
{repository.forks_count} fork(s).
|
||||
</p>
|
||||
|
||||
<Link href={repository.url}>
|
||||
<a className="btn mr-2">Github</a>
|
||||
</Link>
|
||||
{repository.homepage && (
|
||||
<Link href={repository.homepage}>
|
||||
<a className="btn">Website</a>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BestRepository;
|
@ -1,24 +0,0 @@
|
||||
import { createGlobalStyle } from "styled-components";
|
||||
|
||||
const Baseline = createGlobalStyle`
|
||||
body {
|
||||
color: ${({ theme }) => theme.palette.text.primary};
|
||||
background-color: ${({ theme }) => theme.palette.background.primary};
|
||||
font-family: "Montserrat", sans-serif;
|
||||
|
||||
background-image: url("/topography.svg");
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
a {
|
||||
color: ${({ theme }) => theme.palette.text.primary};
|
||||
text-decoration: none;
|
||||
:hover {
|
||||
color: ${({ theme }) => theme.palette.text.secondary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CssBaseline = Baseline as () => JSX.Element;
|
||||
|
||||
export default CssBaseline;
|
@ -1,290 +0,0 @@
|
||||
import styled, { useTheme } from "styled-components";
|
||||
|
||||
import { FunctionalComponent } from "preact";
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
transform: ${(props) => (props.left ? "scale(-1, -1)" : "none")};
|
||||
`;
|
||||
|
||||
const Dots: FunctionalComponent<{
|
||||
left?: boolean;
|
||||
}> = ({ left }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Container left={left}>
|
||||
<svg
|
||||
version="1.0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 645.000000 456.000000"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<g
|
||||
transform="translate(0.000000,456.000000) scale(0.100000,-0.100000)"
|
||||
fill={theme.palette.secondary}
|
||||
stroke="none"
|
||||
>
|
||||
<path
|
||||
d="M3825 4548 c-28 -16 -35 -28 -35 -64 0 -79 106 -88 133 -11 9 25 -18
|
||||
73 -45 81 -29 7 -29 7 -53 -6z"
|
||||
/>
|
||||
<path
|
||||
d="M4455 4548 c-28 -16 -35 -28 -35 -64 0 -39 27 -64 68 -64 58 0 90 76
|
||||
49 117 -19 18 -60 24 -82 11z"
|
||||
/>
|
||||
<path
|
||||
d="M5077 4540 c-20 -16 -27 -30 -27 -55 0 -64 80 -89 120 -38 50 64 -29
|
||||
143 -93 93z"
|
||||
/>
|
||||
<path
|
||||
d="M5706 4539 c-50 -39 -22 -119 43 -119 20 0 36 9 50 26 27 35 26 59
|
||||
-4 89 -30 30 -54 31 -89 4z"
|
||||
/>
|
||||
<path
|
||||
d="M6336 4539 c-49 -39 -22 -119 42 -119 33 0 45 8 61 37 17 33 14 51
|
||||
-14 78 -30 30 -54 31 -89 4z"
|
||||
/>
|
||||
<path
|
||||
d="M3825 3918 c-28 -16 -35 -28 -35 -64 0 -38 27 -64 67 -64 69 0 91
|
||||
100 28 128 -31 14 -35 14 -60 0z"
|
||||
/>
|
||||
<path
|
||||
d="M4447 3910 c-20 -16 -27 -31 -27 -54 0 -66 79 -91 120 -39 50 64 -29
|
||||
143 -93 93z"
|
||||
/>
|
||||
<path
|
||||
d="M5077 3910 c-20 -16 -27 -31 -27 -54 0 -66 79 -91 120 -39 50 64 -29
|
||||
143 -93 93z"
|
||||
/>
|
||||
<path
|
||||
d="M5706 3909 c-50 -39 -24 -119 39 -119 78 0 101 103 29 130 -34 13
|
||||
-38 13 -68 -11z"
|
||||
/>
|
||||
<path
|
||||
d="M6336 3909 c-50 -39 -22 -119 43 -119 75 0 95 104 25 130 -34 13 -38
|
||||
13 -68 -11z"
|
||||
/>
|
||||
<path
|
||||
d="M3816 3279 c-52 -41 -25 -119 41 -119 69 0 91 100 28 128 -34 16 -38
|
||||
15 -69 -9z"
|
||||
/>
|
||||
<path
|
||||
d="M4446 3279 c-32 -25 -36 -77 -7 -103 28 -25 79 -17 104 16 17 23 18
|
||||
31 8 56 -11 26 -46 52 -70 52 -5 0 -20 -9 -35 -21z"
|
||||
/>
|
||||
<path
|
||||
d="M5100 3293 c-50 -18 -68 -84 -32 -116 19 -17 68 -22 88 -9 21 13 34
|
||||
52 28 77 -5 20 -51 58 -66 54 -2 0 -10 -3 -18 -6z"
|
||||
/>
|
||||
<path
|
||||
d="M5730 3293 c-31 -12 -50 -37 -50 -68 0 -78 111 -91 135 -16 9 28 -12
|
||||
70 -40 81 -15 6 -27 10 -28 9 -1 0 -9 -3 -17 -6z"
|
||||
/>
|
||||
<path
|
||||
d="M6360 3293 c-73 -27 -62 -133 14 -133 42 0 64 17 72 55 9 48 -42 94
|
||||
-86 78z"
|
||||
/>
|
||||
<path
|
||||
d="M1939 2658 c-36 -12 -55 -58 -38 -90 16 -31 28 -38 65 -38 77 0 88
|
||||
107 14 132 -8 3 -27 1 -41 -4z"
|
||||
/>
|
||||
<path
|
||||
d="M2567 2658 c-27 -10 -45 -46 -38 -78 5 -28 34 -50 66 -50 78 0 90
|
||||
107 15 132 -8 3 -27 1 -43 -4z"
|
||||
/>
|
||||
<path
|
||||
d="M3210 2662 c-49 -16 -68 -83 -32 -115 25 -22 75 -21 95 1 43 47 -5
|
||||
133 -63 114z"
|
||||
/>
|
||||
<path
|
||||
d="M3840 2662 c-49 -15 -68 -83 -32 -115 24 -22 75 -22 95 1 19 22 23
|
||||
40 14 74 -7 27 -49 49 -77 40z"
|
||||
/>
|
||||
<path
|
||||
d="M4470 2662 c-30 -10 -50 -36 -50 -67 0 -39 26 -65 66 -65 35 0 47 8
|
||||
63 38 25 47 -29 110 -79 94z"
|
||||
/>
|
||||
<path
|
||||
d="M5100 2662 c-30 -10 -50 -36 -50 -67 0 -39 26 -65 66 -65 35 0 47 8
|
||||
63 38 25 47 -29 110 -79 94z"
|
||||
/>
|
||||
<path
|
||||
d="M5730 2662 c-30 -10 -50 -36 -50 -67 0 -78 111 -91 135 -16 14 44
|
||||
-40 97 -85 83z"
|
||||
/>
|
||||
<path
|
||||
d="M6360 2662 c-48 -15 -64 -67 -34 -110 32 -46 124 -16 124 41 0 46
|
||||
-47 82 -90 69z"
|
||||
/>
|
||||
<path
|
||||
d="M1924 2022 c-6 -4 -17 -18 -23 -30 -25 -48 23 -104 79 -93 48 9 68
|
||||
82 32 114 -19 17 -68 22 -88 9z"
|
||||
/>
|
||||
<path
|
||||
d="M2547 2013 c-19 -23 -23 -41 -14 -75 7 -28 43 -46 77 -39 48 9 68 82
|
||||
32 114 -24 22 -75 22 -95 0z"
|
||||
/>
|
||||
<path
|
||||
d="M3177 2012 c-39 -42 -8 -114 48 -114 57 0 88 78 47 115 -25 22 -75
|
||||
21 -95 -1z"
|
||||
/>
|
||||
<path
|
||||
d="M3807 2012 c-33 -37 -14 -104 33 -113 34 -7 70 11 77 39 9 34 5 52
|
||||
-14 75 -21 23 -75 22 -96 -1z"
|
||||
/>
|
||||
<path
|
||||
d="M4437 2012 c-33 -37 -14 -104 33 -113 56 -11 104 45 79 93 -6 12 -17
|
||||
26 -23 30 -21 14 -73 8 -89 -10z"
|
||||
/>
|
||||
<path
|
||||
d="M5067 2012 c-33 -37 -14 -104 33 -113 56 -11 104 45 79 93 -6 12 -17
|
||||
26 -23 30 -21 14 -73 8 -89 -10z"
|
||||
/>
|
||||
<path
|
||||
d="M5697 2012 c-22 -24 -22 -75 1 -95 54 -48 133 -1 116 69 -11 45 -86
|
||||
61 -117 26z"
|
||||
/>
|
||||
<path
|
||||
d="M6326 2008 c-21 -30 -20 -71 2 -91 45 -40 122 -10 122 48 0 59 -91
|
||||
90 -124 43z"
|
||||
/>
|
||||
<path
|
||||
d="M20 1380 c-41 -41 -11 -113 48 -113 62 0 92 60 56 111 -20 29 -76 30
|
||||
-104 2z"
|
||||
/>
|
||||
<path
|
||||
d="M656 1384 c-38 -37 -27 -96 20 -114 67 -26 128 66 76 113 -23 21 -76
|
||||
22 -96 1z"
|
||||
/>
|
||||
<path
|
||||
d="M1294 1392 c-21 -13 -34 -52 -28 -77 7 -30 54 -58 81 -48 53 18 72
|
||||
83 35 116 -19 17 -68 22 -88 9z"
|
||||
/>
|
||||
<path
|
||||
d="M1911 1374 c-24 -31 -25 -35 -10 -67 31 -67 129 -46 129 28 0 64 -79
|
||||
90 -119 39z"
|
||||
/>
|
||||
<path
|
||||
d="M2547 1383 c-33 -38 -22 -94 24 -113 27 -11 33 -11 57 7 33 25 41 76
|
||||
16 104 -22 24 -76 25 -97 2z"
|
||||
/>
|
||||
<path
|
||||
d="M3177 1382 c-34 -38 -16 -101 33 -117 20 -6 59 9 72 29 14 21 8 73
|
||||
-10 89 -25 22 -75 21 -95 -1z"
|
||||
/>
|
||||
<path
|
||||
d="M3806 1381 c-25 -28 -17 -79 16 -104 64 -48 135 45 81 106 -21 23
|
||||
-75 22 -97 -2z"
|
||||
/>
|
||||
<path
|
||||
d="M4437 1382 c-48 -53 12 -140 77 -110 14 6 30 24 37 40 10 25 9 33 -8
|
||||
56 -26 34 -81 42 -106 14z"
|
||||
/>
|
||||
<path
|
||||
d="M5067 1382 c-51 -56 15 -142 82 -107 45 23 49 90 7 117 -21 14 -73 8
|
||||
-89 -10z"
|
||||
/>
|
||||
<path
|
||||
d="M5697 1382 c-48 -53 11 -138 78 -112 45 17 55 79 19 115 -21 21 -77
|
||||
19 -97 -3z"
|
||||
/>
|
||||
<path
|
||||
d="M6326 1378 c-36 -51 -6 -111 56 -111 36 0 68 32 68 69 0 58 -92 89
|
||||
-124 42z"
|
||||
/>
|
||||
<path
|
||||
d="M26 754 c-40 -40 -24 -105 30 -118 62 -16 110 68 65 113 -25 25 -73
|
||||
27 -95 5z"
|
||||
/>
|
||||
<path
|
||||
d="M656 754 c-61 -61 15 -157 83 -106 53 39 30 122 -34 122 -18 0 -41
|
||||
-7 -49 -16z"
|
||||
/>
|
||||
<path
|
||||
d="M1280 743 c-50 -64 29 -143 93 -93 20 16 27 31 27 54 0 66 -79 91
|
||||
-120 39z"
|
||||
/>
|
||||
<path
|
||||
d="M1910 743 c-50 -64 29 -143 93 -93 20 16 27 31 27 54 0 66 -79 91
|
||||
-120 39z"
|
||||
/>
|
||||
<path
|
||||
d="M2548 753 c-36 -43 -27 -93 22 -113 29 -12 65 -1 82 24 14 21 8 73
|
||||
-10 89 -24 22 -75 22 -94 0z"
|
||||
/>
|
||||
<path
|
||||
d="M3177 752 c-34 -38 -16 -101 33 -117 20 -6 59 9 72 29 14 21 8 73
|
||||
-10 89 -25 22 -75 21 -95 -1z"
|
||||
/>
|
||||
<path
|
||||
d="M3807 752 c-17 -19 -22 -68 -9 -88 4 -6 18 -17 30 -23 48 -25 104 23
|
||||
93 79 -9 48 -82 68 -114 32z"
|
||||
/>
|
||||
<path
|
||||
d="M4436 751 c-25 -27 -18 -79 15 -103 36 -27 76 -15 97 28 15 32 15 36
|
||||
-5 62 -26 35 -81 42 -107 13z"
|
||||
/>
|
||||
<path
|
||||
d="M5067 752 c-27 -29 -22 -77 10 -102 64 -50 143 29 93 93 -26 33 -78
|
||||
37 -103 9z"
|
||||
/>
|
||||
<path
|
||||
d="M5695 746 c-22 -33 -15 -75 16 -98 68 -51 144 45 83 106 -25 25 -81
|
||||
20 -99 -8z"
|
||||
/>
|
||||
<path
|
||||
d="M6325 746 c-22 -33 -15 -75 16 -98 68 -51 144 45 83 106 -25 25 -81
|
||||
20 -99 -8z"
|
||||
/>
|
||||
<path
|
||||
d="M26 124 c-61 -61 14 -158 82 -107 35 26 41 74 13 102 -25 25 -73 27
|
||||
-95 5z"
|
||||
/>
|
||||
<path
|
||||
d="M651 114 c-12 -15 -21 -35 -21 -46 0 -24 44 -68 68 -68 33 0 72 38
|
||||
72 71 0 65 -80 93 -119 43z"
|
||||
/>
|
||||
<path
|
||||
d="M1280 113 c-50 -64 29 -143 93 -93 20 16 27 30 27 55 0 64 -80 89
|
||||
-120 38z"
|
||||
/>
|
||||
<path
|
||||
d="M1911 114 c-24 -31 -24 -34 -11 -69 26 -69 130 -47 130 29 0 65 -79
|
||||
91 -119 40z"
|
||||
/>
|
||||
<path
|
||||
d="M2548 123 c-25 -29 -29 -53 -13 -82 23 -45 90 -49 117 -7 14 21 8 73
|
||||
-10 89 -24 22 -75 22 -94 0z"
|
||||
/>
|
||||
<path
|
||||
d="M3177 122 c-34 -38 -16 -101 33 -117 20 -6 59 9 72 29 14 21 8 73
|
||||
-10 89 -25 22 -75 21 -95 -1z"
|
||||
/>
|
||||
<path
|
||||
d="M3807 122 c-17 -19 -22 -68 -9 -88 13 -21 52 -34 77 -28 30 7 58 54
|
||||
48 81 -18 53 -83 72 -116 35z"
|
||||
/>
|
||||
<path
|
||||
d="M4437 122 c-21 -23 -22 -75 -2 -96 38 -38 101 -23 118 28 20 61 -73
|
||||
116 -116 68z"
|
||||
/>
|
||||
<path
|
||||
d="M5067 122 c-27 -29 -22 -77 10 -102 64 -50 143 29 93 93 -26 33 -78
|
||||
37 -103 9z"
|
||||
/>
|
||||
<path
|
||||
d="M5700 120 c-41 -41 -7 -120 52 -120 24 0 68 44 68 68 0 59 -79 93
|
||||
-120 52z"
|
||||
/>
|
||||
<path
|
||||
d="M6329 119 c-28 -28 -22 -76 13 -102 68 -51 143 46 82 107 -22 22 -70
|
||||
20 -95 -5z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dots;
|
@ -1,10 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const Header = styled.h1`
|
||||
font-family: "Prompt", sans-serif;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 3rem;
|
||||
margin-bottom: ${(props) => (props.gutter ? "1rem" : "0")};
|
||||
`;
|
||||
|
||||
export default Header;
|
@ -1,11 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const Image = styled.img.attrs(() => ({ draggable: false }))`
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export default Image;
|
@ -0,0 +1,46 @@
|
||||
import { FC } from "react";
|
||||
|
||||
import styles from "./intro.module.scss";
|
||||
|
||||
const Intro: FC<{ isAvailable: boolean }> = ({ isAvailable }) => {
|
||||
return (
|
||||
<div className={styles.main}>
|
||||
<div className="container">
|
||||
<div className="columns">
|
||||
<div className="column col-8 col-mx-auto text-center">
|
||||
<h1>Guus van Meerveld</h1>
|
||||
|
||||
<h3>
|
||||
Open source <u>web developer</u>
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}`}
|
||||
className="btn btn-primary mr-2"
|
||||
>
|
||||
Github
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="mailto:contact@guusvanmeerveld.dev"
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Contact
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p className="text-gray">
|
||||
Availibility: {isAvailable && "Available"}
|
||||
{!isAvailable && "Not available"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Intro;
|
@ -1,7 +0,0 @@
|
||||
import styled, { DefaultTheme } from "styled-components";
|
||||
|
||||
const Link = styled.a`
|
||||
color: ${({ theme }: { theme: DefaultTheme }) => theme.palette.primary};
|
||||
`;
|
||||
|
||||
export default Link;
|
@ -1,9 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const Paragraph = styled.p`
|
||||
font-family: "Montserrat", sans-serif;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: ${(props) => (props.gutter ? ".5rem" : "0")};
|
||||
`;
|
||||
|
||||
export default Paragraph;
|
@ -1,153 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import styled, { DefaultTheme } from "styled-components";
|
||||
|
||||
import { Spinner } from "react-bootstrap";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import { AiOutlineStar, AiOutlineFork } from "react-icons/ai";
|
||||
|
||||
import { FunctionalComponent } from "preact";
|
||||
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
import Repo from "@interfaces/repo";
|
||||
|
||||
import Header from "@components/Header";
|
||||
import Link from "@components/Link";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
|
||||
const SpinnerContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const ProjectItem = styled(Row)`
|
||||
margin-bottom: 5rem;
|
||||
`;
|
||||
|
||||
const Hero = styled.div`
|
||||
padding: 2rem;
|
||||
background-color: ${({ theme }: { theme: DefaultTheme }) =>
|
||||
theme.palette.background.secondary};
|
||||
|
||||
border-radius: 3px;
|
||||
border: 2px solid
|
||||
${({ theme }: { theme: DefaultTheme }) => theme.palette.border};
|
||||
`;
|
||||
|
||||
const Heading = styled(Header)`
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`;
|
||||
|
||||
const Leading = styled.div`
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const Subtitle = styled.h4`
|
||||
color: ${({ theme }: { theme: DefaultTheme }) =>
|
||||
theme.palette.text.secondary};
|
||||
`;
|
||||
|
||||
const IconsTray = styled.div`
|
||||
display: flex;
|
||||
margin-top: 1rem;
|
||||
font-size: 1.5rem;
|
||||
`;
|
||||
|
||||
const IconItem = styled(Paragraph)`
|
||||
margin-right: 1rem;
|
||||
`;
|
||||
|
||||
const Icon = styled.span`
|
||||
margin-left: 0.5rem;
|
||||
color: ${({ theme }: { theme: DefaultTheme }) => theme.palette.primary};
|
||||
`;
|
||||
|
||||
const TopicsTray = styled.div`
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Topic = styled.div`
|
||||
background-color: ${({ theme }: { theme: DefaultTheme }) =>
|
||||
theme.palette.primary};
|
||||
|
||||
border-radius: 1.25rem;
|
||||
|
||||
margin-top: 0.5rem;
|
||||
margin-left: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
`;
|
||||
|
||||
const ListItem: FunctionalComponent<{
|
||||
repoFullName: string;
|
||||
rtl?: boolean;
|
||||
}> = ({ repoFullName, rtl }) => {
|
||||
const { data, isLoading, error } = useQuery<Repo, AxiosError>(
|
||||
["repo", repoFullName],
|
||||
() =>
|
||||
axios
|
||||
.get(`https://api.github.com/repos/${repoFullName}`)
|
||||
.then((res) => res.data)
|
||||
);
|
||||
|
||||
const body = [
|
||||
<Col md={8}>
|
||||
<Hero>
|
||||
{isLoading && !data && (
|
||||
<SpinnerContainer>
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading {repoFullName}</span>
|
||||
</Spinner>
|
||||
</SpinnerContainer>
|
||||
)}
|
||||
{data && !isLoading && (
|
||||
<>
|
||||
<Leading>
|
||||
<Heading>
|
||||
<a href={data.html_url}>{data.full_name}</a>
|
||||
</Heading>
|
||||
{data.homepage && (
|
||||
<Subtitle>
|
||||
Website: <Link href={data.homepage}>{data.homepage}</Link>
|
||||
</Subtitle>
|
||||
)}
|
||||
</Leading>
|
||||
<Paragraph gutter>{data.description}</Paragraph>
|
||||
<IconsTray>
|
||||
<IconItem>
|
||||
{data.stargazers_count}
|
||||
<Icon>
|
||||
<AiOutlineStar />
|
||||
</Icon>
|
||||
</IconItem>
|
||||
<IconItem>
|
||||
{data.forks_count}
|
||||
<Icon>
|
||||
<AiOutlineFork />
|
||||
</Icon>
|
||||
</IconItem>
|
||||
</IconsTray>
|
||||
<TopicsTray>
|
||||
<Paragraph>Tags:</Paragraph>
|
||||
{data.topics.map((topic) => (
|
||||
<Topic>{topic}</Topic>
|
||||
))}
|
||||
</TopicsTray>
|
||||
</>
|
||||
)}
|
||||
</Hero>
|
||||
</Col>,
|
||||
<Col md={4}></Col>
|
||||
];
|
||||
|
||||
return <ProjectItem>{rtl ? body.reverse() : body}</ProjectItem>;
|
||||
};
|
||||
|
||||
export default ListItem;
|
@ -1,76 +0,0 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import styled from "styled-components";
|
||||
|
||||
import { Spinner } from "react-bootstrap";
|
||||
|
||||
import { FunctionalComponent } from "preact";
|
||||
|
||||
import axios, { AxiosError } from "axios";
|
||||
|
||||
import PinnedRepo from "@interfaces/pinnedRepo";
|
||||
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import ListItem from "@components/Project/Item";
|
||||
|
||||
const SpinnerContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const ErrorText = styled(Paragraph)`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const List: FunctionalComponent = () => {
|
||||
const { data, isLoading, error } = useQuery<PinnedRepo[], AxiosError>(
|
||||
["pinnedRepos"],
|
||||
() =>
|
||||
axios
|
||||
.get(
|
||||
`https://gh-pinned-repos.egoist.sh/?username=${
|
||||
import.meta.env.VITE_GITHUB_USERNAME
|
||||
}`
|
||||
)
|
||||
.then((res) => res.data)
|
||||
);
|
||||
|
||||
if (isLoading)
|
||||
return (
|
||||
<SpinnerContainer>
|
||||
<Spinner animation="border" role="status">
|
||||
<span className="visually-hidden">Loading pinned repos...</span>
|
||||
</Spinner>
|
||||
</SpinnerContainer>
|
||||
);
|
||||
|
||||
if (error)
|
||||
return (
|
||||
<ErrorText>
|
||||
Status {error.response?.status ?? "Unknown"} - {error.response?.data}
|
||||
</ErrorText>
|
||||
);
|
||||
|
||||
if (!error && data) {
|
||||
if (Array.isArray(data))
|
||||
return (
|
||||
<>
|
||||
{data.map((repo, i) => {
|
||||
const repoFullName = `${repo.owner}/${repo.repo}`;
|
||||
return (
|
||||
<ListItem
|
||||
key={repoFullName}
|
||||
rtl={!(i % 2)}
|
||||
repoFullName={repoFullName}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
else return <ErrorText>Unknown data returned from api</ErrorText>;
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default List;
|
@ -0,0 +1,66 @@
|
||||
import { FC } from "react";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import { RecentRepository } from "@interfaces/repository";
|
||||
|
||||
import multipleClassNames from "@utils/multipleClassNames";
|
||||
|
||||
import styles from "./repositories.module.scss";
|
||||
|
||||
const RecentRepositories: FC<{ repositories: RecentRepository[] }> = ({
|
||||
repositories
|
||||
}) => {
|
||||
return (
|
||||
<div className={multipleClassNames("container", styles.main)}>
|
||||
<div className="columns">
|
||||
<div className="column col-6 col-mx-auto text-center">
|
||||
<h3>Some of my recent projects:</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns">
|
||||
<div className="column col-9 col-mx-auto">
|
||||
<div className="columns">
|
||||
{repositories.map((repository) => {
|
||||
return (
|
||||
<div
|
||||
key={repository.name}
|
||||
className="column col-3 col-md-12 col-mx-auto mb-2"
|
||||
>
|
||||
<div
|
||||
className={multipleClassNames(
|
||||
"card",
|
||||
"text-center",
|
||||
styles.card
|
||||
)}
|
||||
>
|
||||
<div className="card-header text-primary">
|
||||
<div className="card-title h5">{repository.name}</div>
|
||||
<div className="card-subtitle text-gray">
|
||||
{repository.stargazers_count} Star(s)
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-body">{repository.description}</div>
|
||||
<div className="card-footer">
|
||||
<Link href={repository.url}>
|
||||
<a className="btn btn-primary">Github</a>
|
||||
</Link>
|
||||
|
||||
{repository.homepage && (
|
||||
<Link href={repository.homepage}>
|
||||
<a className="btn btn-primary ml-2">Website</a>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RecentRepositories;
|
@ -0,0 +1,30 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
import styles from "./themeChanger.module.scss";
|
||||
|
||||
const ThemeChanger: FC = () => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
if (!mounted) return <></>;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
||||
<a
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.main}
|
||||
onClick={() => setTheme(theme == "light" ? "dark" : "light")}
|
||||
>
|
||||
{theme}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeChanger;
|
@ -0,0 +1,6 @@
|
||||
$margin: 1rem;
|
||||
|
||||
.main {
|
||||
margin-top: $margin;
|
||||
margin-bottom: $margin;
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.main {
|
||||
padding-top: 10rem;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
.card {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin-bottom: 3rem;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
.main {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
export default interface PinnedRepo {
|
||||
owner: string;
|
||||
repo: string;
|
||||
link: string;
|
||||
description: string;
|
||||
image: string;
|
||||
website: string;
|
||||
language: string;
|
||||
languageColor: string;
|
||||
stars: string;
|
||||
forks: number;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import "@fontsource/montserrat";
|
||||
import "@fontsource/prompt";
|
||||
import "bootstrap/dist/css/bootstrap.min.css";
|
||||
import "reset-css";
|
||||
|
||||
import App from "./pages/app";
|
||||
|
||||
import { render } from "preact";
|
||||
|
||||
render(<App />, document.getElementById("app") as HTMLElement);
|
@ -0,0 +1,9 @@
|
||||
import type { DefaultSeoProps } from "next-seo";
|
||||
|
||||
const SEO: DefaultSeoProps = {
|
||||
titleTemplate: "%s | Guus van Meerveld",
|
||||
defaultTitle: "Guus van Meerveld",
|
||||
description: "Guus van Meerveld's portfolio"
|
||||
};
|
||||
|
||||
export default SEO;
|
@ -0,0 +1,8 @@
|
||||
.main {
|
||||
height: 100vh;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
@ -1,5 +1,35 @@
|
||||
const NotFound = () => {
|
||||
return <div>Page not found</div>;
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Layout from "@components/Layout";
|
||||
|
||||
import multipleClassNames from "@utils/multipleClassNames";
|
||||
|
||||
import styles from "./404.module.scss";
|
||||
|
||||
const NotFound: NextPage = () => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className={multipleClassNames("empty", styles.main)}>
|
||||
<div>
|
||||
<div className="empty-icon">
|
||||
<i className="icon icon-stop"></i>
|
||||
</div>
|
||||
<p className="empty-title h5">Page not found</p>
|
||||
<p className="empty-subtitle">
|
||||
The page has either been deleted or moved
|
||||
</p>
|
||||
<div className="empty-action">
|
||||
<button onClick={() => router.back()} className="btn btn-primary">
|
||||
Go back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotFound;
|
||||
|
@ -0,0 +1,19 @@
|
||||
import "@styles/globals.scss";
|
||||
|
||||
import SEO from "../next-seo.config";
|
||||
|
||||
import { DefaultSeo } from "next-seo";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
|
||||
import type { AppProps } from "next/app";
|
||||
|
||||
const App = ({ Component, pageProps }: AppProps): JSX.Element => (
|
||||
<>
|
||||
<DefaultSeo {...SEO} />
|
||||
<ThemeProvider>
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
@ -1,40 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
|
||||
import NotFound from "./404";
|
||||
import Index from "./index";
|
||||
|
||||
import useLocalStorageState from "use-local-storage-state";
|
||||
|
||||
import { ThemeProvider } from "styled-components";
|
||||
|
||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
||||
|
||||
import { FunctionalComponent } from "preact";
|
||||
|
||||
import { lightTheme, darkTheme } from "@utils/theme";
|
||||
|
||||
import CssBaseline from "@components/CssBaseline";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const App: FunctionalComponent = () => {
|
||||
const [darkMode] = useLocalStorageState("darkMode", { defaultValue: true });
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
|
||||
<CssBaseline />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -1,147 +1,75 @@
|
||||
import useLocalStorageState from "use-local-storage-state";
|
||||
import { NextSeo } from "next-seo";
|
||||
|
||||
import styled, { DefaultTheme } from "styled-components";
|
||||
import { GetStaticProps, InferGetStaticPropsType, NextPage } from "next";
|
||||
|
||||
import Col from "react-bootstrap/Col";
|
||||
import Container from "react-bootstrap/Container";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import { animated, useSpring } from "react-spring";
|
||||
import axios from "axios";
|
||||
|
||||
import { FunctionalComponent } from "preact";
|
||||
|
||||
import socials from "@utils/socials";
|
||||
|
||||
import Dots from "@components/Dots";
|
||||
import Header from "@components/Header";
|
||||
import Image from "@components/Image";
|
||||
import Intro from "@components/Intro";
|
||||
import Layout from "@components/Layout";
|
||||
import Paragraph from "@components/Paragraph";
|
||||
import ProjectsList from "@components/Project/List";
|
||||
|
||||
const Presentation = styled.div`
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Projects = styled.div`
|
||||
margin-top: 5rem;
|
||||
padding: 5rem 0;
|
||||
`;
|
||||
|
||||
const Hero = styled.div`
|
||||
/* background-color: ${({ theme }: { theme: DefaultTheme }) =>
|
||||
theme.palette.background.secondary}; */
|
||||
justify-content: ${(props) => (props.right ? "right" : "left")};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const Logo = styled.div`
|
||||
margin: 2rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
import RecentRepositories from "@components/RecentRepositories";
|
||||
import BestRepository from "@components/BestRepository";
|
||||
|
||||
const ProjectsHeader = styled(Header)`
|
||||
text-align: center;
|
||||
margin-bottom: 5rem;
|
||||
`;
|
||||
import { GithubAPIRepository } from "@interfaces/repository";
|
||||
|
||||
const IconsTray = styled.div`
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
import createConfigCatClient from "@utils/createConfigCatClient";
|
||||
|
||||
const Icon = styled.div`
|
||||
margin-left: 1.5rem;
|
||||
font-size: 2rem;
|
||||
`;
|
||||
|
||||
const Index: FunctionalComponent = () => {
|
||||
const [darkMode] = useLocalStorageState("darkMode");
|
||||
export const getStaticProps: GetStaticProps = async () => {
|
||||
const { data } = await axios.get<GithubAPIRepository[]>(
|
||||
`https://api.github.com/users/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}/repos`
|
||||
);
|
||||
|
||||
const fadeIn = useSpring({
|
||||
to: { opacity: 1 },
|
||||
from: { opacity: 0 },
|
||||
config: {
|
||||
mass: 5
|
||||
const configCatClient = createConfigCatClient();
|
||||
|
||||
const isAvailable: boolean =
|
||||
(await configCatClient?.getValueAsync("amiavailable", true)) ?? true;
|
||||
|
||||
const bestRepository = data.sort(
|
||||
(a, b) => b.stargazers_count - a.stargazers_count
|
||||
)[0];
|
||||
|
||||
return {
|
||||
props: {
|
||||
isAvailable,
|
||||
repositories: data
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
)
|
||||
.map(({ name, description, html_url, stargazers_count, homepage }) => ({
|
||||
name,
|
||||
url: html_url,
|
||||
stargazers_count,
|
||||
homepage,
|
||||
description
|
||||
}))
|
||||
.slice(0, 3),
|
||||
bestRepository: {
|
||||
name: bestRepository.name,
|
||||
description: bestRepository.description,
|
||||
url: bestRepository.html_url,
|
||||
homepage: bestRepository.homepage,
|
||||
stargazers_count: bestRepository.stargazers_count,
|
||||
forks_count: bestRepository.forks_count,
|
||||
language: bestRepository.language,
|
||||
open_issues_count: bestRepository.open_issues_count,
|
||||
pushed_at: bestRepository.pushed_at
|
||||
}
|
||||
});
|
||||
},
|
||||
revalidate: 60 * 5
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
const Index: NextPage = ({
|
||||
repositories,
|
||||
isAvailable,
|
||||
bestRepository
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => (
|
||||
<Layout>
|
||||
<Presentation>
|
||||
<Container>
|
||||
<animated.div style={fadeIn}>
|
||||
<Row>
|
||||
<Col md={8}>
|
||||
<Hero>
|
||||
<div>
|
||||
<Header gutter>Welcome</Header>
|
||||
<Paragraph gutter>
|
||||
My name is Guus van Meerveld, and I am a web developer.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
This is my portfolio website, to showcase my projects.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</Hero>
|
||||
</Col>
|
||||
<Col md={4}>
|
||||
<Dots />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Logo>
|
||||
<Image
|
||||
// onClick={() => setDarkMode(!darkMode)}
|
||||
// style={{ cursor: "pointer" }}
|
||||
height={64}
|
||||
src={darkMode ? "logo-dark.png" : "logo-light.png"}
|
||||
alt=""
|
||||
/>
|
||||
</Logo>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col md={4}>
|
||||
<Dots left />
|
||||
</Col>
|
||||
<Col md={8}>
|
||||
<Hero right>
|
||||
<div>
|
||||
<Header>Follow me:</Header>
|
||||
<IconsTray>
|
||||
{socials.map((social) => (
|
||||
<Icon>
|
||||
<a href={social.url}>{social.icon}</a>
|
||||
</Icon>
|
||||
))}
|
||||
</IconsTray>
|
||||
</div>
|
||||
</Hero>
|
||||
</Col>
|
||||
</Row>
|
||||
</animated.div>
|
||||
</Container>
|
||||
</Presentation>
|
||||
|
||||
<Projects>
|
||||
<Container>
|
||||
<Row>
|
||||
<ProjectsHeader>Projects</ProjectsHeader>
|
||||
</Row>
|
||||
<Row>
|
||||
<ProjectsList />
|
||||
</Row>
|
||||
</Container>
|
||||
</Projects>
|
||||
<NextSeo title="Home" />
|
||||
<Intro isAvailable={isAvailable} />
|
||||
<RecentRepositories repositories={repositories} />
|
||||
<BestRepository repository={bestRepository} />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
);
|
||||
|
||||
export default Index;
|
||||
|
@ -1 +0,0 @@
|
||||
import JSX = preact.JSX;
|
@ -1,21 +0,0 @@
|
||||
import Theme from "./interfaces/theme";
|
||||
|
||||
import "styled-components";
|
||||
|
||||
declare module "styled-components" {
|
||||
export interface DefaultTheme {
|
||||
palette: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
background: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
};
|
||||
text: {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
};
|
||||
border: string;
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
[data-theme="dark"] {
|
||||
|
||||
body {
|
||||
background-color: $bg-dark;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.empty {
|
||||
background-color: $dark-color-secondary;
|
||||
color: $text-primary;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: $bg-dark;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $primary-color-dark;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: $primary-color !important;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: $primary-color !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: $bg-dark-secondary;
|
||||
color: $primary-color;
|
||||
|
||||
border-color: $primary-color;
|
||||
|
||||
&:hover {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: $primary-color;
|
||||
border-color: $primary-color;
|
||||
|
||||
color: $text-primary;
|
||||
|
||||
&:hover {
|
||||
background-color: $primary-color-dark;
|
||||
border-color: $primary-color-dark;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: $primary-color-dark;
|
||||
border-color: $primary-color-dark;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
$light-color: #eee;
|
||||
$dark-color: #212121;
|
||||
$primary-color: #7e57c2;
|
||||
|
||||
$dark-color-secondary: lighten($dark-color, 2%);
|
||||
|
||||
$primary-color-dark: darken($primary-color, 10%);
|
||||
|
||||
$bg-dark: $dark-color;
|
||||
$bg-dark-secondary: $dark-color-secondary;
|
||||
|
||||
$text-primary: $light-color;
|
||||
$text-secondary: darken($light-color, 10%);
|
@ -0,0 +1,6 @@
|
||||
@use "spectre.css/src/spectre.scss";
|
||||
@use "spectre.css/src/spectre-icons.scss";
|
||||
|
||||
@import "variables";
|
||||
|
||||
@import "dark";
|
@ -0,0 +1,21 @@
|
||||
import * as configcat from "configcat-node";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
const createConfigCatClient = () => {
|
||||
if (!process.env.CONFIG_CAT_SDK_KEY) return;
|
||||
|
||||
const logger = configcat.createConsoleLogger(
|
||||
process.env.NODE_ENV == "production" ? 0 : 3
|
||||
);
|
||||
|
||||
const configCatClient = configcat.createClient(
|
||||
process.env.CONFIG_CAT_SDK_KEY,
|
||||
{
|
||||
logger
|
||||
}
|
||||
);
|
||||
|
||||
return configCatClient;
|
||||
};
|
||||
|
||||
export default createConfigCatClient;
|
@ -0,0 +1,5 @@
|
||||
const multipleClassNames = (...classNames: string[]): string => {
|
||||
return classNames.join(" ");
|
||||
};
|
||||
|
||||
export default multipleClassNames;
|
@ -1,34 +0,0 @@
|
||||
import { BsTwitter, BsYoutube, BsMastodon, BsGithub } from "react-icons/bs";
|
||||
import { SiKofi } from "react-icons/si";
|
||||
|
||||
const socials: { name: string; url: string; icon: JSX.Element }[] = [
|
||||
{
|
||||
name: "Twitter",
|
||||
url: `https://twitter.com/${import.meta.env.VITE_TWITTER_USERNAME}`,
|
||||
icon: <BsTwitter />
|
||||
},
|
||||
{
|
||||
name: "Youtube",
|
||||
url: `https://youtube.com/channel/${
|
||||
import.meta.env.VITE_YOUTUBE_CHANNEL_ID
|
||||
}`,
|
||||
icon: <BsYoutube />
|
||||
},
|
||||
{
|
||||
name: "Mastodon",
|
||||
url: import.meta.env.VITE_MASTODON_URL,
|
||||
icon: <BsMastodon />
|
||||
},
|
||||
{
|
||||
name: "Ko-fi",
|
||||
url: `https://ko-fi.com/${import.meta.env.VITE_KOFI_USERNAME}`,
|
||||
icon: <SiKofi />
|
||||
},
|
||||
{
|
||||
name: "Github",
|
||||
url: `https://github.com/${import.meta.env.VITE_GITHUB_USERNAME}`,
|
||||
icon: <BsGithub />
|
||||
}
|
||||
];
|
||||
|
||||
export default socials;
|
@ -1,33 +0,0 @@
|
||||
import { DefaultTheme } from "styled-components";
|
||||
|
||||
export const lightTheme: DefaultTheme = {
|
||||
palette: {
|
||||
background: {
|
||||
primary: "#FBF8F1",
|
||||
secondary: "#F7ECDE"
|
||||
},
|
||||
border: "#dcdcdc",
|
||||
primary: "#E9DAC1",
|
||||
secondary: "#54BAB9",
|
||||
text: {
|
||||
primary: "#111111",
|
||||
secondary: "#4d4d4d"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const darkTheme: DefaultTheme = {
|
||||
palette: {
|
||||
background: {
|
||||
primary: "#000000",
|
||||
secondary: "#1d1d1d"
|
||||
},
|
||||
border: "#454545",
|
||||
primary: "#DC0067",
|
||||
secondary: "#00dc75",
|
||||
text: {
|
||||
primary: "#eee",
|
||||
secondary: "#ccc"
|
||||
}
|
||||
}
|
||||
};
|
@ -1,11 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_GITHUB_USERNAME: string;
|
||||
readonly VITE_TWITTER_USERNAME: string;
|
||||
readonly VITE_YOUTUBE_CHANNEL_ID: string;
|
||||
readonly VITE_MASTODON_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
@ -1,30 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@interfaces/*": ["./src/interfaces/*"],
|
||||
"@utils/*": ["./src/utils/*"],
|
||||
"react": ["./node_modules/preact/compat/"],
|
||||
"react-dom": ["./node_modules/preact/compat/"]
|
||||
},
|
||||
"jsx": "preserve",
|
||||
"jsxFactory": "h",
|
||||
"jsxFragmentFactory": "Fragment"
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": ".next/tsbuildinfo.json",
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@styles/*": ["styles/*"],
|
||||
"@components/*": ["components/*"],
|
||||
"@interfaces/*": ["interfaces/*"],
|
||||
"@utils/*": ["utils/*"],
|
||||
"@src/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
import preact from "@preact/preset-vite";
|
||||
import { defineConfig } from "vite";
|
||||
import viteImagemin from "vite-plugin-imagemin";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
import alias from "@rollup/plugin-alias";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
preact(),
|
||||
alias({
|
||||
entries: [
|
||||
{ find: "react", replacement: "preact/compat" },
|
||||
{ find: "react-dom/test-utils", replacement: "preact/test-utils" },
|
||||
{ find: "react-dom", replacement: "preact/compat" },
|
||||
{ find: "react/jsx-runtime", replacement: "preact/jsx-runtime" }
|
||||
]
|
||||
}),
|
||||
tsconfigPaths(),
|
||||
viteImagemin({
|
||||
optipng: {
|
||||
optimizationLevel: 7
|
||||
},
|
||||
mozjpeg: {
|
||||
quality: 20
|
||||
},
|
||||
pngquant: {
|
||||
quality: [0.8, 0.9],
|
||||
speed: 4
|
||||
},
|
||||
svgo: {
|
||||
plugins: [
|
||||
{
|
||||
name: "removeViewBox"
|
||||
},
|
||||
{
|
||||
name: "removeEmptyAttrs",
|
||||
active: false
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
],
|
||||
esbuild: {
|
||||
logOverride: { "this-is-undefined-in-esm": "silent" }
|
||||
}
|
||||
});
|
Loading…
Reference in new issue