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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user