From 87932f1934e9a18a44a78621e9229598649c5bbd Mon Sep 17 00:00:00 2001 From: Wouter van Veelen Date: Sat, 20 Dec 2025 02:02:11 +0100 Subject: [PATCH] Complete english version of site --- README.md | 77 +----------- assets/Squagon.pdf | Bin 0 -> 37771 bytes eslint.config.ts | 18 +-- index.html | 3 +- package-lock.json | 4 +- package.json | 2 +- src/App.css | 7 ++ src/App.tsx | 4 + src/components/CareerEntry.tsx | 35 ++++++ src/components/CollapsableCard.tsx | 19 ++- src/components/OutsideLink.tsx | 12 ++ src/layout/Content.tsx | 9 +- src/layout/TopBar.tsx | 8 +- src/pages/About.tsx | 19 ++- src/pages/Career.tsx | 184 +++++++++++++++++++++++------ src/pages/Home.tsx | 41 ++++++- src/pages/Puzzles.tsx | 17 +++ src/pages/Storytelling.tsx | 28 +++++ src/utils.ts | 7 ++ 19 files changed, 355 insertions(+), 139 deletions(-) create mode 100644 assets/Squagon.pdf create mode 100644 src/components/CareerEntry.tsx create mode 100644 src/components/OutsideLink.tsx create mode 100644 src/utils.ts diff --git a/README.md b/README.md index df7963a..60f98b3 100644 --- a/README.md +++ b/README.md @@ -1,75 +1,2 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## React Compiler - -The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information. - -Note: This will impact Vite dev & build performances. - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ["./tsconfig.node.json", "./tsconfig.app.json"], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]); -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.ts -import reactX from "eslint-plugin-react-x"; -import reactDom from "eslint-plugin-react-dom"; - -export default defineConfig([ - globalIgnores(["dist"]), - { - files: ["**/*.{ts,tsx}"], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs["recommended-typescript"], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ["./tsconfig.node.json", "./tsconfig.app.json"], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]); -``` +# Hi +Nice of you to check out the source of the website. Hopefully you enjoy what you see! \ No newline at end of file diff --git a/assets/Squagon.pdf b/assets/Squagon.pdf new file mode 100644 index 0000000000000000000000000000000000000000..50c166f7b0c3c5915e6bdcc3ad42d8b0c8d09000 GIT binary patch literal 37771 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�)Kv;-jP}F*7H%2sA1aoLQBsU}~%nF4MvFLlBofs0dd8C1Ox*;OU|e ztzcv5=45H=;^O4&X5?z>V=;-3)WNB!}MOX=FVX{kpvU5gia&~cP zu7bX^lcR~bnUSTLv9YVAo4J#Vk+X%FtEH>4i=&yjg`+vB=~rBmSX2To-i*ym4a~Vz JRbBnvxB$2kJp=#% literal 0 HcmV?d00001 diff --git a/eslint.config.ts b/eslint.config.ts index e684b1f..4d22e49 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,18 +1,18 @@ -import js from "@eslint/js"; -import globals from "globals"; -import reactHooks from "eslint-plugin-react-hooks"; -import reactRefresh from "eslint-plugin-react-refresh"; -import tseslint from "typescript-eslint"; -import { defineConfig, globalIgnores } from "eslint/config"; +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; +import { defineConfig, globalIgnores } from 'eslint/config'; export default defineConfig([ - globalIgnores(["dist"]), + globalIgnores(['dist', 'node_modules']), { - files: ["**/*.{ts,tsx}"], + files: ['**/*.{ts,tsx}'], extends: [ js.configs.recommended, tseslint.configs.recommended, - reactHooks.configs["recommended-latest"], + reactHooks.configs['recommended-latest'], reactRefresh.configs.vite, ], languageOptions: { diff --git a/index.html b/index.html index b5f8a1c..7cd4d28 100644 --- a/index.html +++ b/index.html @@ -2,10 +2,9 @@ - - cblt-website + Wouter van Veelen
diff --git a/package-lock.json b/package-lock.json index ee4505c..9f5a140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2412,7 +2412,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index d90c503..063190a 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "cblt-website", + "name": "webhome", "private": true, "version": "0.0.0", "type": "module", diff --git a/src/App.css b/src/App.css index 7cc43a6..f27c48a 100644 --- a/src/App.css +++ b/src/App.css @@ -81,3 +81,10 @@ body { min-width: 100%; z-index: -100; } + +p { + margin: 0; + + p { + margin-top: 1.5em; + } +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 59defb3..eec37ad 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,6 +5,8 @@ import { Content } from './layout/Content.tsx'; import { Home } from './pages/Home.tsx'; import { About } from './pages/About.tsx'; import { Career } from './pages/Career.tsx'; +import { Storytelling } from './pages/Storytelling.tsx'; +import { Puzzles } from './pages/Puzzles.tsx'; function App() { return ( @@ -15,6 +17,8 @@ function App() { } /> } /> + } /> + } /> } /> diff --git a/src/components/CareerEntry.tsx b/src/components/CareerEntry.tsx new file mode 100644 index 0000000..80a1d6f --- /dev/null +++ b/src/components/CareerEntry.tsx @@ -0,0 +1,35 @@ +import { type ReactElement, type ReactNode } from 'react'; +import { CollapsableCard, type CollapsableCardProps } from './CollapsableCard.tsx'; +import { + TimelineConnector, + TimelineContent, + TimelineDot, + TimelineItem, + TimelineOppositeContent, + TimelineSeparator, +} from '@mui/lab'; + +export type CareerEntryProps = CollapsableCardProps & { + start: ReactNode; + finalEntry?: boolean; +}; +export function CareerEntry({ + finalEntry, + startCollapsed = true, + variant = 'outlined', + start, + ...props +}: CareerEntryProps): ReactElement { + return ( + + {start} + + + {!finalEntry && } + + + + + + ); +} diff --git a/src/components/CollapsableCard.tsx b/src/components/CollapsableCard.tsx index 0f06243..a320b5a 100644 --- a/src/components/CollapsableCard.tsx +++ b/src/components/CollapsableCard.tsx @@ -15,6 +15,7 @@ export function CollapsableCard({ startCollapsed, ...props }: CollapsableCardProps): ReactElement { + const hasContent = children != null; const [collapsed, setCollapsed] = useState(startCollapsed ?? false); const toggleCollapse = () => setCollapsed((oldState) => !oldState); @@ -26,16 +27,22 @@ export function CollapsableCard({ title={header} subheader={subheader} action={ - - {collapsed ? - - : } - + hasContent ? + + {collapsed ? + + : } + + : undefined } /> )} - {children} + {hasContent && ( + + {children} + + )} ); diff --git a/src/components/OutsideLink.tsx b/src/components/OutsideLink.tsx new file mode 100644 index 0000000..4d1205f --- /dev/null +++ b/src/components/OutsideLink.tsx @@ -0,0 +1,12 @@ +import { OpenInNewOutlined } from '@mui/icons-material'; +import { Link, type LinkProps } from '@mui/material'; +import type { ReactElement } from 'react'; + +export function OutsideLink({ children, target = '_blank', ...props }: LinkProps): ReactElement { + return ( + + {children} + + + ); +} diff --git a/src/layout/Content.tsx b/src/layout/Content.tsx index e75fec8..0102d59 100644 --- a/src/layout/Content.tsx +++ b/src/layout/Content.tsx @@ -6,7 +6,14 @@ export type ContentProps = PropsWithChildren; export function Content({ children }: ContentProps): ReactElement { return ( - + theme.palette.text.primary, + }} + > {children} diff --git a/src/layout/TopBar.tsx b/src/layout/TopBar.tsx index 6b2ba2b..7a318e7 100644 --- a/src/layout/TopBar.tsx +++ b/src/layout/TopBar.tsx @@ -2,7 +2,13 @@ import React, { type ReactElement } from 'react'; import { Box, Tab, Tabs } from '@mui/material'; import { useLocation, useNavigate } from 'react-router'; -const ValidTabs = ['home', 'career', 'about'] as const satisfies string[]; +const ValidTabs = [ + 'home', + 'career', + 'storytelling', + 'puzzles', + 'about', +] as const satisfies string[]; export function TopBar(): ReactElement { const { pathname } = useLocation(); diff --git a/src/pages/About.tsx b/src/pages/About.tsx index bd5b83d..033fe5f 100644 --- a/src/pages/About.tsx +++ b/src/pages/About.tsx @@ -1,5 +1,22 @@ import { Fragment, type ReactElement } from 'react'; export function About(): ReactElement { - return Hello, About.; + return ( + +

+

+ This website is by and about me, Wouter van Veelen. The reason for having this + website is three-fold. First, I want to have a place where I can experiment with web + techniques and frameworks. Secondly, I want a place where I can publicly share + creative works. Third, I wanted to delete my LinkedIn, without becoming completely + unfindable for potential employers or collaborators. +

+

+ About the technical specs, this website built using Vite + React (compiler) and + served by an Apache2 web server running on a self-built Arch-linux server. The + storage on the server runs on zfs in a 3+1 HDD configuration with an NVME boot + drive. +

+
+ ); } diff --git a/src/pages/Career.tsx b/src/pages/Career.tsx index 6120d2e..86f7e6c 100644 --- a/src/pages/Career.tsx +++ b/src/pages/Career.tsx @@ -1,43 +1,153 @@ -import { - Timeline, - TimelineConnector, - TimelineContent, - TimelineDot, - TimelineItem, - TimelineOppositeContent, - TimelineSeparator, -} from '@mui/lab'; -import type { ReactElement } from 'react'; -import { CollapsableCard } from '../components/CollapsableCard.tsx'; +import { Timeline } from '@mui/lab'; +import { Fragment, type ReactElement } from 'react'; +import { Switch, Typography } from '@mui/material'; +import { useToggle } from '../utils.ts'; +import { OutsideLink } from '../components/OutsideLink.tsx'; +import { CareerEntry } from '../components/CareerEntry.tsx'; export function Career(): ReactElement { + const [full, toggleFull] = useToggle(true); return ( - - - 2014 - - - - - - +
+
+ +
+ Include activism and volunteer work +
+
+ + + + {full && ( + + Until 2018, IAPC + + } > - Test - - - - - 10:00 am - - - - - Code - - +

+ IAPC is a computer parts and + service store completely run by volunteers on the campus of the + Universtiy of Twente. +

+

+ During my active years I helped in the committees for logistics, RMA and + PR, as well as being board member for logistics in 2015-2016. +

+ + )} + {full && ( + + Until 2023,{' '} + + D.B.V. de Stretchers + + + } + > +

+ + D.B.V. de Stretchers + {' '} + is a sports association on the University of Twente where all sports are + practiced and the focus is on fun, learning and activities +

+

+ During my active years I was a trainer and assisted in the events + committee as well as PR and the website. During the academic year + 2020-2021 I was the chairman of the association +

+
+ )} + +

+ With courses focused around algorithms, machine learning and data + processing. +

+

+ Completed all required courses, but did not complete the final thesis + because of personal reasons. +

+
+ {full && ( + +

+ The{' '} + + muziekbank + {' '} + is a library of vinyls and cds where customers can go to discover new + and old music, experience event and rent items like a normal library. +

+

+ While being active I manned the counter to assist customers and was + involved in processing newly bought cds to be added to the collection +

+
+ )} + +

+ Full stack developer on a SaaS project based on Java, Spring Boot, GraphQL, + Typescript and React. +

+

+ Personally dove deep in understanding the fundamentals of frameworks and + languages and applying them for major version upgrades and developing on + internal tools and libraries. +

+
+ {full && ( + + Current, Volleyball association{' '} + Twente '05 + + } + finalEntry={full} + /> + )} + + ); } diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4920e89..8fd84ce 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,11 +1,42 @@ import { type ReactElement } from 'react'; -import { Card, CardContent, CardHeader } from '@mui/material'; +import { Link, Stack, Tooltip } from '@mui/material'; +import { GitHub, Source } from '@mui/icons-material'; export function Home(): ReactElement { return ( - - - Hello, others - + +

Welcome

+

To My Website

+

+

+ On this website you can find bits about me and bits by me. I am a software developer + and the career tab can tell you some of my history in the world. As proof of being a + software developer, this website is entirely self-hosted, including a git repository + (and except the DNS servers, I suppose), on a server that I have physically built + and maintain. +

+

+ Besides being a software developer, I also like stories and puzzles a whole lot, so + you can also find tabs dedicated to those. Hope you enjoy! I hope to add more + puzzle-focused elements to the site in the future. +

+

+ Use the top bar to find what you are looking for or contact me via email. No direct + mailto link here to avoid scraper bots, but my first name and this domain name + should do the trick. +

+ + + + + + + + + + + + +
); } diff --git a/src/pages/Puzzles.tsx b/src/pages/Puzzles.tsx index e69de29..63765de 100644 --- a/src/pages/Puzzles.tsx +++ b/src/pages/Puzzles.tsx @@ -0,0 +1,17 @@ +import { Fragment, type ReactElement } from 'react'; +import { OutsideLink } from '../components/OutsideLink.tsx'; + +export function Puzzles(): ReactElement { + return ( + +

+

+ I have a love for puzzles and puzzle events. In the past I have particapated in and + organized the{' '} + Pandora puzzle event at the + University of Twente. +

+

As such I want to create and share more puzzles here, in time.

+
+ ); +} diff --git a/src/pages/Storytelling.tsx b/src/pages/Storytelling.tsx index e69de29..e235a6e 100644 --- a/src/pages/Storytelling.tsx +++ b/src/pages/Storytelling.tsx @@ -0,0 +1,28 @@ +import { Fragment, type ReactElement } from 'react'; +import { Card, CardContent } from '@mui/material'; +import { OutsideLink } from '../components/OutsideLink.tsx'; + +export function Storytelling(): ReactElement { + return ( + +

+

+ I like to consume and tell stories. Besides reading and watching tv and movies, I + also like to create my own stories. +

+

+ I've played my fair share of Dungeons and Dragons and have also DM'ed for a while, + and for the{' '} + Pandora puzzle event I have + also been responsible for or involved with the story aspect during the two times I + was part of the event organisation +

+

+ I've also participated in a small short story writing competition for{' '} + Bellettrie, the + student library of the University of Twente. The theme was 'Fairytale' and you can + read my entry here. +

+
+ ); +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..c1c9543 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,7 @@ +import { useCallback, useState } from 'react'; + +export function useToggle(initialValue?: boolean): [boolean, () => void] { + const [state, setState] = useState(initialValue ?? false); + const toggle = useCallback(() => setState((oldState) => !oldState), []); + return [state, toggle]; +}