Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion frontend/e2e/mocks/api-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Page } from '@playwright/test';
import { TestPublicEvent, TestRSVPResponse, TestGuest } from '../fixtures/test-data';

const API_URL = process.env.VITE_API_URL || 'http://localhost:3006';

/**
* Mock the GET /api/events/:slug endpoint to return a specific event.
Expand Down
2 changes: 1 addition & 1 deletion frontend/e2e/specs/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
import { mockAuthFlow, setupCommonMocks } from '../mocks/api-handlers';
import { LoginPage, VerifyPage } from '../pages/login.page';
import { injectAuth, clearAuth, TEST_USER, TEST_TOKEN } from '../fixtures/auth.fixture';
import { injectAuth, clearAuth, TEST_TOKEN } from '../fixtures/auth.fixture';

test.describe('Authentication Flow', () => {
test('visit /login and see email input', async ({ page }) => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/e2e/specs/create-event.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';
import { setupCommonMocks, mockPartyAPI, blockExternalScripts } from '../mocks/api-handlers';
import { injectAuth, TEST_USER } from '../fixtures/auth.fixture';
import { setupCommonMocks, blockExternalScripts } from '../mocks/api-handlers';
import { injectAuth } from '../fixtures/auth.fixture';

test.describe('Create Event', () => {
test('authenticated user fills event form and submits', async ({ page }) => {
Expand Down
1 change: 0 additions & 1 deletion frontend/e2e/specs/host-dashboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ function setupHostPageMocks(page: import('@playwright/test').Page, partyOverride

// Mock Supabase parties query (both custom_url and invite_code lookups)
page.route('**/*.supabase.co/rest/v1/parties*', (route, request) => {
const url = request.url();
if (request.method() === 'GET') {
return route.fulfill({
status: 200,
Expand Down
1 change: 0 additions & 1 deletion frontend/e2e/specs/rsvp-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
mockEventAPI,
mockRSVPSubmission,
setupCommonMocks,
mockUserPreferences,
} from '../mocks/api-handlers';
import {
makePublicEvent,
Expand Down
9 changes: 9 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export default tseslint.config(
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
},
},
{
Expand Down
49 changes: 38 additions & 11 deletions frontend/eslint.hooks.config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Focused ESLint config: ONLY react-hooks/rules-of-hooks, over src.
// Focused ESLint config: react-hooks/rules-of-hooks + no-unused-vars, over src.
//
// The full `npm run lint` carries ~678 pre-existing debt items (no-explicit-any,
// no-unused-vars, exhaustive-deps warnings), so it can't gate PRs as-is. But
// rules-of-hooks is the high-value incident class: a hook declared below an early
// return builds fine yet black-screens at runtime (arugula-38633 v2, 2026-05-19).
// The full `npm run lint` carries pre-existing debt (no-explicit-any,
// exhaustive-deps warnings), so it can't gate PRs as-is. This config runs just
// the high-value, fully-clean rules so CI can block them without the debt noise:
// - react-hooks/rules-of-hooks: a hook declared below an early return builds
// fine yet black-screens at runtime (arugula-38633 v2, 2026-05-19).
// - @typescript-eslint/no-unused-vars: dead-code ratchet, paid down to 0 in
// cavatelli P1; this gate keeps it there.
//
// This config runs just that one rule so CI can block it without the debt noise.
// We pull in ONLY the typescript-eslint parser (so .tsx parses) — none of its
// *rules* — which is why the `any`/unused-vars debt doesn't surface here.
// e2e/ is intentionally NOT matched: Playwright's `use` fixture param trips the
// rule (false positive), and e2e is not shipped React anyway.
// hooks rule (false positive), and e2e is not shipped React anyway.
//
// Run via: npx eslint --config eslint.hooks.config.js "src/**/*.{ts,tsx}"
import reactHooks from 'eslint-plugin-react-hooks';
Expand All @@ -18,6 +18,33 @@ import tseslint from 'typescript-eslint';
export default tseslint.config({
files: ['src/**/*.{ts,tsx}'],
languageOptions: { parser: tseslint.parser },
plugins: { 'react-hooks': reactHooks },
rules: { 'react-hooks/rules-of-hooks': 'error' },
plugins: {
'react-hooks': reactHooks,
'@typescript-eslint': tseslint.plugin,
},
rules: {
'react-hooks/rules-of-hooks': 'error',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
// cavatelli P2: long-tail lint errors paid down to 0; these gates keep them there.
// Core ESLint rules (no plugin needed):
'no-empty': 'error',
'no-case-declarations': 'error',
'no-useless-catch': 'error',
'no-constant-binary-expression': 'error',
'no-irregular-whitespace': 'error',
'no-useless-escape': 'error',
'prefer-const': 'error',
// typescript-eslint rules:
'@typescript-eslint/no-empty-object-type': 'error',
'@typescript-eslint/prefer-as-const': 'error',
'@typescript-eslint/ban-ts-comment': 'error',
},
});
15 changes: 1 addition & 14 deletions frontend/src/__tests__/field-sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function extractInterfaceFields(source: string, interfaceName: string): string[]
if (!match) return [];

const body = match[1];
const fieldRegex = /^\s*(\w+)[\?:]?\s*:/gm;
const fieldRegex = /^\s*(\w+)[?:]?\s*:/gm;
const fields: string[] = [];
let fieldMatch;
while ((fieldMatch = fieldRegex.exec(body)) !== null) {
Expand Down Expand Up @@ -94,19 +94,6 @@ function extractDbPartyToPartyFields(source: string): string[] {
return Array.from(fields);
}

// Extract fields destructured in the backend PATCH handler
function extractPatchHandlerFields(source: string): string[] {
// Find the PATCH handler destructuring
const patchRegex = /router\.patch\s*\(\s*'\/:id'[\s\S]*?const\s*\{([^}]+)\}\s*=\s*req\.body/;
const match = source.match(patchRegex);
if (!match) return [];

return match[1]
.split(',')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('//'));
}

describe('Field Mapping Consistency', () => {
const safeColumns = parseSafeColumns(SAFE_PARTY_COLUMNS);

Expand Down
17 changes: 8 additions & 9 deletions frontend/src/components/AICallStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Phone, PhoneCall, PhoneOff, CheckCircle, XCircle, Loader2, RotateCcw, Clock } from 'lucide-react';
import { Phone, PhoneCall, PhoneOff, CheckCircle, Loader2, RotateCcw, Clock } from 'lucide-react';

export interface CallStatusData {
id: string;
Expand Down Expand Up @@ -39,8 +39,7 @@ export const AICallStatus: React.FC<AICallStatusProps> = ({

// Poll for status updates
useEffect(() => {
let intervalId: NodeJS.Timeout;
let timeIntervalId: NodeJS.Timeout;
const intervals: { poll?: NodeJS.Timeout; time?: NodeJS.Timeout } = {};

const fetchStatus = async () => {
try {
Expand All @@ -66,8 +65,8 @@ export const AICallStatus: React.FC<AICallStatusProps> = ({

// If call is complete, stop polling
if (['completed', 'failed', 'no_answer'].includes(data.status)) {
clearInterval(intervalId);
clearInterval(timeIntervalId);
clearInterval(intervals.poll);
clearInterval(intervals.time);
onComplete(data);
}
} catch (err) {
Expand All @@ -78,16 +77,16 @@ export const AICallStatus: React.FC<AICallStatusProps> = ({

// Start polling
fetchStatus();
intervalId = setInterval(fetchStatus, 3000);
intervals.poll = setInterval(fetchStatus, 3000);

// Update elapsed time
timeIntervalId = setInterval(() => {
intervals.time = setInterval(() => {
setElapsedTime((prev) => prev + 1);
}, 1000);

return () => {
clearInterval(intervalId);
clearInterval(timeIntervalId);
clearInterval(intervals.poll);
clearInterval(intervals.time);
};
}, [aiPhoneCallId, onComplete]);

Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/AddGuestForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState, useEffect } from 'react';
import { usePizza } from '../contexts/PizzaContext';
import { Guest } from '../types';
import { UserPlus, Loader2, ThumbsUp, ThumbsDown, User, X } from 'lucide-react';
import { IconInput } from './IconInput';
import { getExcludedToppingIds, DIETARY_OPTIONS } from '../constants/options';
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/CheckInScanner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface CheckInScannerProps {
onClose: () => void;
}

export function CheckInScanner({ inviteCode, currentGuestId, onVouchSuccess, onClose }: CheckInScannerProps) {
export function CheckInScanner({ currentGuestId, onVouchSuccess, onClose }: CheckInScannerProps) {
const scannerRef = useRef<Html5Qrcode | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [status, setStatus] = useState<string>('Starting camera...');
Expand Down Expand Up @@ -39,7 +39,7 @@ export function CheckInScanner({ inviteCode, currentGuestId, onVouchSuccess, onC
if (segments.length === 3 && segments[0] === 'checkin') {
return { inviteCode: segments[1], guestId: segments[2] };
}
} catch {}
} catch { /* not a URL — fall through */ }
return null;
}, []);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/CustomUrlInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function CustomUrlInput({
setValidationError(result.valid ? null : result.error || 'Invalid URL');
setIsValid(result.valid);
onValidationChange?.(result.valid, result.error);
} catch (err) {
} catch {
setValidationError('Failed to validate URL');
setIsValid(false);
onValidationChange?.(false, 'Failed to validate URL');
Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/DonationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ const DonationFormInner: React.FC<DonationFormInnerProps> = ({
onSuccess,
onBack,
guestId,
clientSecret,
}) => {
const stripe = useStripe();
const elements = useElements();
Expand Down
Loading
Loading