Migrate JavaScript to TypeScript with full type safety

- Rename all .js/.jsx files to .ts/.tsx across resources/js and theme dirs
- Add TypeScript 6.0 with strict mode, tsconfig.json
- Add type definitions for Inertia page props, Alpine.js, Turbolinks
- Update vite.config.js entries to .ts/.tsx extensions
- Update all Blade @vite() calls to match new .ts/.tsx entry points
- Add TypeScript ESLint config (replacing unused Vue plugin)
- Add @types/react, @types/react-dom, @types/lodash
- Add typecheck script and integrate into check pipeline
- Full tsc --noEmit, ESLint, and production build pass cleanly
This commit is contained in:
root
2026-06-18 17:00:00 +02:00
parent 4aa6f01779
commit e6d92f27b3
41 changed files with 986 additions and 680 deletions
-31
View File
@@ -1,31 +0,0 @@
import { Head } from '@inertiajs/react'
export default function Home({ auth, hotelName }) {
return (
<>
<Head title="Home" />
<div className="col-span-12">
<div className="bg-white dark:bg-gray-900 rounded-xl shadow-sm p-8 text-center">
<h1 className="text-4xl font-bold mb-4" style={{ color: 'var(--color-text)' }}>
Welkom bij {hotelName}
</h1>
<p className="text-lg" style={{ color: 'var(--color-text-muted)' }}>
Dit is een Inertia.js pagina zelfde layout, zelfde Tailwind, zelfde stijlen.
</p>
<div className="mt-8 flex justify-center gap-4">
<a
href="/"
className="px-6 py-3 rounded-xl font-semibold transition-all duration-200 hover:scale-105"
style={{
backgroundColor: 'var(--color-primary)',
color: 'var(--button-text-color)',
}}
>
Naar huis
</a>
</div>
</div>
</div>
</>
)
}
+43
View File
@@ -0,0 +1,43 @@
import { Head } from "@inertiajs/react";
interface HomeProps {
auth: Record<string, unknown>;
hotelName: string;
}
export default function Home({ hotelName }: HomeProps) {
return (
<>
<Head title="Home" />
<div className="col-span-12">
<div className="rounded-xl bg-white p-8 text-center shadow-sm dark:bg-gray-900">
<h1
className="mb-4 text-4xl font-bold"
style={{ color: "var(--color-text)" }}
>
Welkom bij {hotelName}
</h1>
<p
className="text-lg"
style={{ color: "var(--color-text-muted)" }}
>
Dit is een Inertia.js pagina zelfde layout, zelfde Tailwind,
zelfde stijlen.
</p>
<div className="mt-8 flex justify-center gap-4">
<a
href="/"
className="rounded-xl px-6 py-3 font-semibold transition-all duration-200 hover:scale-105"
style={{
backgroundColor: "var(--color-primary)",
color: "var(--button-text-color)",
}}
>
Naar huis
</a>
</div>
</div>
</div>
</>
);
}
-119
View File
@@ -1,119 +0,0 @@
import { Head, usePage } from '@inertiajs/react'
export default function Index({ articles, photos }) {
const { avatarImager } = usePage().props
return (
<>
<Head title="Welkom" />
<div className="col-span-12 space-y-14">
<div className="col-span-12">
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
<div className="flex gap-x-2">
<div className="hotel-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] max-h-[50px] items-center justify-center rounded-full" />
<div className="flex flex-col">
<p className="font-semibold text-black dark:text-gray-200">Laatste nieuws</p>
<p className="dark:text-gray-500">Blijf op de hoogte van het laatste hotel nieuws.</p>
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{articles.length > 0 ? articles.map(article => (
<a key={article.id} href={`/community/article/${article.slug}`}
className="group relative block h-[210px] w-full overflow-hidden rounded shadow-sm transition-all duration-200 ease-in-out hover:scale-[101%]"
style={{ backgroundColor: 'var(--color-surface)' }}
onMouseEnter={e => {
const el = e.currentTarget.querySelector('.article-img-inner')
if (el) el.classList.add('article-image-slide')
}}
onMouseLeave={e => {
const el = e.currentTarget.querySelector('.article-img-inner')
if (el) el.classList.remove('article-image-slide')
}}>
<div className="article-img-inner h-[100px] w-full bg-cover bg-center transition-[background-position] duration-300"
style={{ backgroundImage: `url('/storage/${article.image}')`, backgroundPosition: '300px 220px' }}>
</div>
<div className="px-3 md:px-4">
<p className="truncate text-base font-semibold md:text-lg dark:text-gray-200">{article.title}</p>
<div className="flex items-center gap-x-2">
{article.user && (
<>
<div className="mt-2 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full md:mt-3 md:h-10 md:w-10"
style={{ backgroundColor: 'var(--color-background)' }}>
<img src={`${avatarImager}${article.user.look}&headonly=1`}
alt="" className="h-full w-full object-cover" />
</div>
<p className="mt-2 text-sm font-semibold md:mt-4 md:text-base"
style={{ color: 'var(--color-text-muted)' }}>{article.user.username}</p>
</>
)}
</div>
</div>
</a>
)) : (
<>
{[1, 2, 3, 4].map(i => (
<div key={i}
className="h-[210px] w-full overflow-hidden rounded bg-white shadow-sm dark:bg-gray-900">
<div className="article-image"
style={{ background: "url('https://i.imgur.com/uGLDOUu.png')" }} />
<div className="mt-4 px-4">
<p className="truncate text-lg font-semibold dark:text-gray-200">Geen artikelen</p>
<div className="flex items-center gap-x-2">
<div className="mt-3 flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<img src={`${avatarImager}&headonly=1`} alt="" />
</div>
<p className="mt-4 font-semibold dark:text-gray-400">Epicnabbo</p>
</div>
</div>
</div>
))}
</>
)}
</div>
</div>
</div>
{photos.length > 0 && (
<div className="col-span-12">
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
<div className="flex gap-x-2">
<div className="camera-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] max-h-[50px] items-center justify-center rounded-full" />
<div className="flex flex-col">
<p className="font-semibold text-black dark:text-gray-200">Laatste foto's</p>
<p className="dark:text-gray-500">Bekijk de mooiste momenten vastgelegd door gebruikers.</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5">
{photos.map(photo => (
<a key={photo.id} href={photo.url} data-fancybox="gallery" className="group block cursor-pointer">
<div className="relative overflow-hidden rounded-lg border border-gray-600 shadow-md transition-all duration-300 hover:border-[#eeb425]">
<div className="relative aspect-[4/3] overflow-hidden">
<img src={photo.url} alt={`Photo by ${photo.user?.username || 'Unknown'}`}
className="h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110" />
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
</div>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-2">
<div className="flex items-center gap-2">
<div className="flex h-7 w-7 items-center justify-center overflow-hidden rounded-full border border-gray-500 bg-gray-700">
<img src={`${avatarImager}${photo.user?.look || ''}&direction=2&headonly=1&head_direction=2&gesture=sml`}
alt={photo.user?.username || 'Unknown'} className="h-full w-full object-cover" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-white">{photo.user?.username || 'Unknown'}</p>
</div>
</div>
</div>
</div>
</a>
))}
</div>
</div>
</div>
)}
</div>
</>
)
}
+206
View File
@@ -0,0 +1,206 @@
import { Head, usePage } from "@inertiajs/react";
interface User {
look: string;
username: string;
}
interface Article {
id: number;
slug: string;
title: string;
image: string;
user?: User;
}
interface Photo {
id: number;
url: string;
user?: User;
}
interface IndexProps {
articles: Article[];
photos: Photo[];
}
interface SharedProps extends Record<string, unknown> {
avatarImager: string;
}
export default function Index({ articles, photos }: IndexProps) {
const { avatarImager } = usePage<SharedProps>().props;
return (
<>
<Head title="Welkom" />
<div className="col-span-12 space-y-14">
<div className="col-span-12">
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
<div className="flex gap-x-2">
<div className="hotel-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] items-center justify-center rounded-full" />
<div className="flex flex-col">
<p className="font-semibold text-black dark:text-gray-200">
Laatste nieuws
</p>
<p className="dark:text-gray-500">
Blijf op de hoogte van het laatste hotel nieuws.
</p>
</div>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{articles.length > 0
? articles.map((article) => (
<a
key={article.id}
href={`/community/article/${article.slug}`}
className="group relative block h-[210px] w-full overflow-hidden rounded shadow-sm transition-all duration-200 ease-in-out hover:scale-[101%]"
style={{ backgroundColor: "var(--color-surface)" }}
onMouseEnter={(e) => {
const el = e.currentTarget.querySelector(
".article-img-inner",
);
if (el) el.classList.add("article-image-slide");
}}
onMouseLeave={(e) => {
const el = e.currentTarget.querySelector(
".article-img-inner",
);
if (el) el.classList.remove("article-image-slide");
}}
>
<div
className="article-img-inner h-[100px] w-full bg-cover bg-center transition-[background-position] duration-300"
style={{
backgroundImage: `url('/storage/${article.image}')`,
backgroundPosition: "300px 220px",
}}
/>
<div className="px-3 md:px-4">
<p className="truncate text-base font-semibold md:text-lg dark:text-gray-200">
{article.title}
</p>
<div className="flex items-center gap-x-2">
{article.user && (
<>
<div
className="mt-2 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full md:mt-3 md:h-10 md:w-10"
style={{
backgroundColor: "var(--color-background)",
}}
>
<img
src={`${avatarImager}${article.user.look}&headonly=1`}
alt=""
className="h-full w-full object-cover"
/>
</div>
<p
className="mt-2 text-sm font-semibold md:mt-4 md:text-base"
style={{
color: "var(--color-text-muted)",
}}
>
{article.user.username}
</p>
</>
)}
</div>
</div>
</a>
))
: [1, 2, 3, 4].map((i) => (
<div
key={i}
className="h-[210px] w-full overflow-hidden rounded bg-white shadow-sm dark:bg-gray-900"
>
<div
className="article-image"
style={{
background:
"url('https://i.imgur.com/uGLDOUu.png')",
}}
/>
<div className="mt-4 px-4">
<p className="truncate text-lg font-semibold dark:text-gray-200">
Geen artikelen
</p>
<div className="flex items-center gap-x-2">
<div className="mt-3 flex h-10 w-10 items-center justify-center overflow-hidden rounded-full bg-gray-100 dark:bg-gray-800">
<img
src={`${avatarImager}&headonly=1`}
alt=""
/>
</div>
<p className="mt-4 font-semibold dark:text-gray-400">
Epicnabbo
</p>
</div>
</div>
</div>
))}
</div>
</div>
</div>
{photos.length > 0 && (
<div className="col-span-12">
<div className="flex w-full flex-col gap-y-4 overflow-hidden rounded-lg p-3">
<div className="flex gap-x-2">
<div className="camera-icon relative flex min-h-[50px] min-w-[50px] max-w-[50px] items-center justify-center rounded-full" />
<div className="flex flex-col">
<p className="font-semibold text-black dark:text-gray-200">
Laatste foto's
</p>
<p className="dark:text-gray-500">
Bekijk de mooiste momenten vastgelegd door gebruikers.
</p>
</div>
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5">
{photos.map((photo) => (
<a
key={photo.id}
href={photo.url}
data-fancybox="gallery"
className="group block cursor-pointer"
>
<div className="relative overflow-hidden rounded-lg border border-gray-600 shadow-md transition-all duration-300 hover:border-[#eeb425]">
<div className="relative aspect-[4/3] overflow-hidden">
<img
src={photo.url}
alt={`Photo by ${photo.user?.username || "Unknown"}`}
className="h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-110"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
</div>
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent p-2">
<div className="flex items-center gap-2">
<div className="flex h-7 w-7 items-center justify-center overflow-hidden rounded-full border border-gray-500 bg-gray-700">
<img
src={`${avatarImager}${photo.user?.look || ""}&direction=2&headonly=1&head_direction=2&gesture=sml`}
alt={photo.user?.username || "Unknown"}
className="h-full w-full object-cover"
/>
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-white">
{photo.user?.username || "Unknown"}
</p>
</div>
</div>
</div>
</div>
</a>
))}
</div>
</div>
</div>
)}
</div>
</>
);
}