Fix ESLint issues in TypeScript codebase

- Add browser globals for frontend TypeScript files in ESLint config
- Fix unused parameter in MapImageService.generateErrorImage()
- Remove auto-fixable formatting issues (trailing spaces, indentation)

All ESLint errors now resolved, only warnings for 'any' types remain.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Code 2025-07-07 20:05:27 -04:00
parent ab5cddeec5
commit 5a8bcb7fff
10 changed files with 83 additions and 48 deletions

View file

@ -62,6 +62,41 @@ export default [
'no-trailing-spaces': 'error' 'no-trailing-spaces': 'error'
} }
}, },
{
files: ['src/frontend/**/*.ts'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module'
},
globals: {
...globals.browser
}
},
plugins: {
'@typescript-eslint': typescript
},
rules: {
// TypeScript rules
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/no-explicit-any': 'warn',
// General rules
'no-console': 'off', // Allow console.log for debugging
'no-var': 'error',
'prefer-const': 'error',
'eqeqeq': 'error',
'no-unused-vars': 'off', // Use TypeScript version instead
// Style rules
'indent': ['error', 2],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'comma-dangle': ['error', 'never'],
'no-trailing-spaces': 'error'
}
},
{ {
files: ['tests/**/*.ts'], files: ['tests/**/*.ts'],
languageOptions: { languageOptions: {

View file

@ -20,7 +20,7 @@ function initializeAdminApp() {
header.render(); header.render();
} }
} }
// Admin page doesn't have a footer in the current design // Admin page doesn't have a footer in the current design
// but we could add one if needed // but we could add one if needed
} }

View file

@ -10,11 +10,11 @@ function initializeApp() {
// Render header // Render header
const header = SharedHeader.createMainHeader(); const header = SharedHeader.createMainHeader();
header.render(); header.render();
// Render footer // Render footer
const footer = SharedFooter.createStandardFooter(); const footer = SharedFooter.createStandardFooter();
footer.render(); footer.render();
// The rest of the app logic (map, form, etc.) remains in the existing app.js // The rest of the app logic (map, form, etc.) remains in the existing app.js
// This just adds the shared components // This just adds the shared components
} }

View file

@ -13,11 +13,11 @@ function initializePrivacyApp() {
const headerContainer = document.createElement('div'); const headerContainer = document.createElement('div');
headerContainer.id = 'header-container'; headerContainer.id = 'header-container';
privacyHeader.parentNode?.replaceChild(headerContainer, privacyHeader); privacyHeader.parentNode?.replaceChild(headerContainer, privacyHeader);
const header = SharedHeader.createPrivacyHeader(); const header = SharedHeader.createPrivacyHeader();
header.render(); header.render();
} }
// Add footer if there's a container for it // Add footer if there's a container for it
const footerContainer = document.getElementById('footer-container'); const footerContainer = document.getElementById('footer-container');
if (footerContainer) { if (footerContainer) {

View file

@ -52,7 +52,7 @@ export class SharedFooter {
`; `;
container.appendChild(footer); container.appendChild(footer);
// Update translations if i18n is available // Update translations if i18n is available
if ((window as any).i18n?.updatePageTranslations) { if ((window as any).i18n?.updatePageTranslations) {
(window as any).i18n.updatePageTranslations(); (window as any).i18n.updatePageTranslations();

View file

@ -58,12 +58,12 @@ export class SharedHeader {
`; `;
container.appendChild(header); container.appendChild(header);
// Initialize components after rendering // Initialize components after rendering
if (this.config.showThemeToggle) { if (this.config.showThemeToggle) {
this.initializeThemeToggle(); this.initializeThemeToggle();
} }
// Update translations if i18n is available // Update translations if i18n is available
if ((window as any).i18n?.updatePageTranslations) { if ((window as any).i18n?.updatePageTranslations) {
(window as any).i18n.updatePageTranslations(); (window as any).i18n.updatePageTranslations();
@ -79,15 +79,15 @@ export class SharedHeader {
} }
private renderButton(btn: ButtonConfig): string { private renderButton(btn: ButtonConfig): string {
const attrs = btn.attributes const attrs = btn.attributes
? Object.entries(btn.attributes).map(([k, v]) => `${k}="${v}"`).join(' ') ? Object.entries(btn.attributes).map(([k, v]) => `${k}="${v}"`).join(' ')
: ''; : '';
const i18nAttr = btn.i18nKey ? `data-i18n="${btn.i18nKey}"` : ''; const i18nAttr = btn.i18nKey ? `data-i18n="${btn.i18nKey}"` : '';
const text = btn.text || ''; const text = btn.text || '';
const icon = btn.icon || ''; const icon = btn.icon || '';
const idAttr = btn.id ? `id="${btn.id}"` : ''; const idAttr = btn.id ? `id="${btn.id}"` : '';
if (btn.href) { if (btn.href) {
return `<a href="${btn.href}" class="${btn.class || 'header-btn'}" ${attrs} ${i18nAttr}>${icon}${icon && text ? ' ' : ''}${text}</a>`; return `<a href="${btn.href}" class="${btn.class || 'header-btn'}" ${attrs} ${i18nAttr}>${icon}${icon && text ? ' ' : ''}${text}</a>`;
} else { } else {
@ -110,7 +110,7 @@ export class SharedHeader {
themeToggle.addEventListener('click', () => { themeToggle.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme'); const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme); document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme); localStorage.setItem('theme', newTheme);
updateThemeIcon(); updateThemeIcon();

View file

@ -19,7 +19,7 @@ export class I18nService {
*/ */
private loadTranslations(): void { private loadTranslations(): void {
const localesDir = path.join(__dirname, 'locales'); const localesDir = path.join(__dirname, 'locales');
for (const locale of this.availableLocales) { for (const locale of this.availableLocales) {
try { try {
const filePath = path.join(localesDir, `${locale}.json`); const filePath = path.join(localesDir, `${locale}.json`);
@ -37,7 +37,7 @@ export class I18nService {
*/ */
public t(keyPath: string, locale: string = this.defaultLocale, params?: Record<string, string>): string { public t(keyPath: string, locale: string = this.defaultLocale, params?: Record<string, string>): string {
const translations = this.translations.get(locale) || this.translations.get(this.defaultLocale); const translations = this.translations.get(locale) || this.translations.get(this.defaultLocale);
if (!translations) { if (!translations) {
console.warn(`No translations found for locale: ${locale}`); console.warn(`No translations found for locale: ${locale}`);
return keyPath; return keyPath;
@ -133,10 +133,10 @@ export class I18nService {
if (this.isLocaleSupported(lang.code)) { if (this.isLocaleSupported(lang.code)) {
return lang.code; return lang.code;
} }
// Check for language match (e.g., "es" matches "es-MX") // Check for language match (e.g., "es" matches "es-MX")
const languageCode = lang.code.split('-')[0]; const languageCode = lang.code.split('-')[0];
const matchingLocale = this.availableLocales.find(locale => const matchingLocale = this.availableLocales.find(locale =>
locale.startsWith(languageCode) locale.startsWith(languageCode)
); );
if (matchingLocale) { if (matchingLocale) {

View file

@ -22,7 +22,7 @@ export function createI18nRoutes(): Router {
// Get translations for the locale // Get translations for the locale
const translations = i18nService.getTranslations(locale); const translations = i18nService.getTranslations(locale);
if (!translations) { if (!translations) {
res.status(404).json({ res.status(404).json({
error: 'Translations not found for locale', error: 'Translations not found for locale',

View file

@ -36,14 +36,14 @@ app.use((req: Request, res: Response, next: NextFunction) => {
// Detect user's preferred locale from Accept-Language header or cookie // Detect user's preferred locale from Accept-Language header or cookie
const cookieLocale = req.headers.cookie?.split(';') const cookieLocale = req.headers.cookie?.split(';')
.find(c => c.trim().startsWith('locale='))?.split('=')[1]; .find(c => c.trim().startsWith('locale='))?.split('=')[1];
const detectedLocale = cookieLocale || i18nService.detectLocale(req.get('Accept-Language')); const detectedLocale = cookieLocale || i18nService.detectLocale(req.get('Accept-Language'));
// Add locale to request object for use in routes // Add locale to request object for use in routes
(req as any).locale = detectedLocale; (req as any).locale = detectedLocale;
(req as any).t = (key: string, params?: Record<string, string>) => (req as any).t = (key: string, params?: Record<string, string>) =>
i18nService.t(key, detectedLocale, params); i18nService.t(key, detectedLocale, params);
next(); next();
}); });
@ -211,20 +211,20 @@ function setupRoutes(): void {
try { try {
// Get locale from query parameter or use detected locale // Get locale from query parameter or use detected locale
const requestedLocale = req.query.locale as string; const requestedLocale = req.query.locale as string;
const locale = requestedLocale && i18nService.isLocaleSupported(requestedLocale) const locale = requestedLocale && i18nService.isLocaleSupported(requestedLocale)
? requestedLocale ? requestedLocale
: (req as any).locale; : (req as any).locale;
// Helper function for translations // Helper function for translations
const t = (key: string) => i18nService.t(key, locale); const t = (key: string) => i18nService.t(key, locale);
const locations = await locationModel.getActive(); const locations = await locationModel.getActive();
const formatTimeRemaining = (createdAt: string, isPersistent?: boolean): string => { const formatTimeRemaining = (createdAt: string, isPersistent?: boolean): string => {
if (isPersistent) { if (isPersistent) {
return t('time.persistent'); return t('time.persistent');
} }
const created = new Date(createdAt); const created = new Date(createdAt);
const now = new Date(); const now = new Date();
const diffMs = (48 * 60 * 60 * 1000) - (now.getTime() - created.getTime()); const diffMs = (48 * 60 * 60 * 1000) - (now.getTime() - created.getTime());
@ -248,10 +248,10 @@ function setupRoutes(): void {
const escapeHtml = (text: string): string => { const escapeHtml = (text: string): string => {
return text.replace(/&/g, '&amp;') return text.replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
.replace(/"/g, '&quot;') .replace(/"/g, '&quot;')
.replace(/'/g, '&#39;'); .replace(/'/g, '&#39;');
}; };
const tableRows = locations.map((location, index) => ` const tableRows = locations.map((location, index) => `
@ -383,12 +383,12 @@ function setupRoutes(): void {
console.log('Handling form submission for non-JS users'); console.log('Handling form submission for non-JS users');
const { address, description, locale: formLocale } = req.body; const { address, description, locale: formLocale } = req.body;
// Get locale from form or use detected locale // Get locale from form or use detected locale
const locale = formLocale && i18nService.isLocaleSupported(formLocale) const locale = formLocale && i18nService.isLocaleSupported(formLocale)
? formLocale ? formLocale
: (req as any).locale; : (req as any).locale;
// Helper function for translations // Helper function for translations
const t = (key: string) => i18nService.t(key, locale); const t = (key: string) => i18nService.t(key, locale);
@ -473,7 +473,7 @@ function setupRoutes(): void {
console.log('Generating static map image'); console.log('Generating static map image');
try { try {
const locations = await locationModel.getActive(); const locations = await locationModel.getActive();
// Parse query parameters for customization // Parse query parameters for customization
const width = parseInt(req.query.width as string) || 800; const width = parseInt(req.query.width as string) || 800;
const height = parseInt(req.query.height as string) || 600; const height = parseInt(req.query.height as string) || 600;

View file

@ -18,18 +18,18 @@ export class MapImageService {
*/ */
async generateMapImage(locations: Location[], options: Partial<MapOptions> = {}): Promise<Buffer> { async generateMapImage(locations: Location[], options: Partial<MapOptions> = {}): Promise<Buffer> {
const opts = { ...this.defaultOptions, ...options }; const opts = { ...this.defaultOptions, ...options };
console.info('Generating Mapbox static map focused on location data'); console.info('Generating Mapbox static map focused on location data');
console.info('Canvas size:', opts.width, 'x', opts.height); console.info('Canvas size:', opts.width, 'x', opts.height);
console.info('Number of locations:', locations.length); console.info('Number of locations:', locations.length);
const mapboxBuffer = await this.fetchMapboxStaticMapAutoFit(opts, locations); const mapboxBuffer = await this.fetchMapboxStaticMapAutoFit(opts, locations);
if (mapboxBuffer) { if (mapboxBuffer) {
return mapboxBuffer; return mapboxBuffer;
} else { } else {
// Return a simple error image if Mapbox fails // Return a simple error image if Mapbox fails
return this.generateErrorImage(opts); return this.generateErrorImage();
} }
} }
@ -54,10 +54,10 @@ export class MapImageService {
overlays += `pin-s-${label}+${color}(${location.longitude},${location.latitude}),`; overlays += `pin-s-${label}+${color}(${location.longitude},${location.latitude}),`;
} }
}); });
// Remove trailing comma // Remove trailing comma
overlays = overlays.replace(/,$/, ''); overlays = overlays.replace(/,$/, '');
console.info('Generated overlays string:', overlays); console.info('Generated overlays string:', overlays);
// Build Mapbox Static Maps URL with auto-fit // Build Mapbox Static Maps URL with auto-fit
@ -65,7 +65,7 @@ export class MapImageService {
if (overlays) { if (overlays) {
// Check if we have only one location // Check if we have only one location
const validLocations = locations.filter(loc => loc.latitude && loc.longitude); const validLocations = locations.filter(loc => loc.latitude && loc.longitude);
if (validLocations.length === 1) { if (validLocations.length === 1) {
// For single location, use fixed zoom level to avoid zooming too close // For single location, use fixed zoom level to avoid zooming too close
const location = validLocations[0]; const location = validLocations[0];
@ -81,13 +81,13 @@ export class MapImageService {
const fallbackLng = -85.67402711517647; const fallbackLng = -85.67402711517647;
mapboxUrl = `https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/${fallbackLng},${fallbackLat},10/${options.width}x${options.height}?access_token=${mapboxToken}`; mapboxUrl = `https://api.mapbox.com/styles/v1/mapbox/streets-v12/static/${fallbackLng},${fallbackLat},10/${options.width}x${options.height}?access_token=${mapboxToken}`;
} }
console.info('Fetching Mapbox static map...'); console.info('Fetching Mapbox static map...');
if (overlays && locations.filter(loc => loc.latitude && loc.longitude).length === 1) { if (overlays && locations.filter(loc => loc.latitude && loc.longitude).length === 1) {
console.info('Using fixed zoom level for single location'); console.info('Using fixed zoom level for single location');
} }
console.info('URL:', mapboxUrl.replace(mapboxToken, 'TOKEN_HIDDEN')); console.info('URL:', mapboxUrl.replace(mapboxToken, 'TOKEN_HIDDEN'));
return new Promise((resolve) => { return new Promise((resolve) => {
const request = https.get(mapboxUrl, { timeout: 10000 }, (response) => { const request = https.get(mapboxUrl, { timeout: 10000 }, (response) => {
if (response.statusCode === 200) { if (response.statusCode === 200) {
@ -102,12 +102,12 @@ export class MapImageService {
resolve(null); resolve(null);
} }
}); });
request.on('error', (err) => { request.on('error', (err) => {
console.error('Error fetching Mapbox map:', err.message); console.error('Error fetching Mapbox map:', err.message);
resolve(null); resolve(null);
}); });
request.on('timeout', () => { request.on('timeout', () => {
console.error('Mapbox request timeout'); console.error('Mapbox request timeout');
request.destroy(); request.destroy();
@ -120,7 +120,7 @@ export class MapImageService {
/** /**
* Generate a simple error image when Mapbox fails * Generate a simple error image when Mapbox fails
*/ */
private generateErrorImage(options: MapOptions): Buffer { private generateErrorImage(): Buffer {
// Generate a simple 1x1 transparent PNG as fallback // Generate a simple 1x1 transparent PNG as fallback
// This is a valid PNG header + IHDR + IDAT + IEND for a 1x1 transparent pixel // This is a valid PNG header + IHDR + IDAT + IEND for a 1x1 transparent pixel
const transparentPng = Buffer.from([ const transparentPng = Buffer.from([
@ -134,7 +134,7 @@ export class MapImageService {
0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, // IEND chunk 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, // IEND chunk
0x42, 0x60, 0x82 0x42, 0x60, 0x82
]); ]);
console.info('Generated transparent PNG fallback due to Mapbox failure'); console.info('Generated transparent PNG fallback due to Mapbox failure');
return transparentPng; return transparentPng;
} }