chore: add pre-commit hooks, tighten ESLint, activate Sentry DSN, publish CI coverage (Phase 1)

- Install husky v9 + lint-staged: pre-commit runs eslint --fix and prettier on staged files
- Tighten ESLint base config: no-console→error, ban-ts-comment (ts-ignore banned, ts-expect-error with description allowed), reportUnusedDisableDirectives→error
- Migrate web app from deprecated `next lint` to `eslint src/` with flat config and react-hooks plugin
- Convert all 5 @ts-ignore to @ts-expect-error with descriptions, remove stale disable comments
- Add NEXT_PUBLIC_SENTRY_DSN to docker-compose.prod.yml and .env.example
- Add coverage artifact upload step to CI test job
- Pre-existing violations (102 warnings) downgraded to warn in web config for Phase 2 cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 14:49:29 +02:00
parent 605fd7cea1
commit 82acc56b8d
38 changed files with 2901 additions and 1251 deletions
+70 -19
View File
@@ -18,10 +18,16 @@ export function MfaSetup() {
useEffect(() => {
if (!uri) return;
let cancelled = false;
QRCode.toDataURL(uri, { width: 200, margin: 2 }).then((dataUrl) => {
if (!cancelled) setQrDataUrl(dataUrl);
}).catch(() => {/* ignore — manual key is shown as fallback */});
return () => { cancelled = true; };
QRCode.toDataURL(uri, { width: 200, margin: 2 })
.then((dataUrl) => {
if (!cancelled) setQrDataUrl(dataUrl);
})
.catch(() => {
/* ignore — manual key is shown as fallback */
});
return () => {
cancelled = true;
};
}, [uri]);
const { data: mfaStatus, refetch } = trpc.user.getMfaStatus.useQuery();
@@ -60,12 +66,24 @@ export function MfaSetup() {
<div className="rounded-xl border border-green-200 dark:border-green-800 bg-green-50 dark:bg-green-900/20 p-6">
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/40">
<svg className="h-5 w-5 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
<svg
className="h-5 w-5 text-green-600 dark:text-green-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
/>
</svg>
</div>
<div>
<h3 className="text-sm font-semibold text-green-800 dark:text-green-300">MFA Enabled</h3>
<h3 className="text-sm font-semibold text-green-800 dark:text-green-300">
MFA Enabled
</h3>
<p className="text-sm text-green-700 dark:text-green-400">
Two-factor authentication is active on your account.
</p>
@@ -92,14 +110,27 @@ export function MfaSetup() {
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-6">
<div className="flex items-start gap-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-amber-100 dark:bg-amber-900/40">
<svg className="h-5 w-5 text-amber-600 dark:text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
<svg
className="h-5 w-5 text-amber-600 dark:text-amber-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Two-Factor Authentication (TOTP)</h3>
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
Two-Factor Authentication (TOTP)
</h3>
<p className="mt-1 text-sm text-gray-600 dark:text-gray-400">
Add an extra layer of security by requiring a code from your authenticator app when signing in.
Add an extra layer of security by requiring a code from your authenticator app when
signing in.
</p>
<button
type="button"
@@ -116,17 +147,25 @@ export function MfaSetup() {
{step === "show-secret" && (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-6 space-y-5">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Step 1: Scan the QR code</h3>
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
Step 1: Scan the QR code
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password, etc.).
Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password,
etc.).
</p>
{/* QR Code — rendered locally, no external service */}
<div className="flex justify-center">
<div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-white p-3">
{qrDataUrl ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={qrDataUrl} alt="TOTP QR Code" width={200} height={200} className="rounded" />
<img
src={qrDataUrl}
alt="TOTP QR Code"
width={200}
height={200}
className="rounded"
/>
) : (
<div className="h-[200px] w-[200px] flex items-center justify-center text-xs text-gray-400">
Generating
@@ -146,7 +185,10 @@ export function MfaSetup() {
<button
type="button"
onClick={() => { setStep("verify"); setError(null); }}
onClick={() => {
setStep("verify");
setError(null);
}}
className="inline-flex items-center gap-2 rounded-lg bg-brand-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-brand-700 transition-colors"
>
Continue
@@ -156,13 +198,18 @@ export function MfaSetup() {
{step === "verify" && (
<div className="rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-6 space-y-5">
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">Step 2: Verify your code</h3>
<h3 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
Step 2: Verify your code
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
Enter the 6-digit code from your authenticator app to confirm setup.
</p>
<div>
<label htmlFor="mfa-verify-token" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
<label
htmlFor="mfa-verify-token"
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
>
Verification Code
</label>
<input
@@ -191,7 +238,11 @@ export function MfaSetup() {
</button>
<button
type="button"
onClick={() => { setStep("show-secret"); setToken(""); setError(null); }}
onClick={() => {
setStep("show-secret");
setToken("");
setError(null);
}}
className="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
>
Back