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:
parent
ab5cddeec5
commit
5a8bcb7fff
10 changed files with 83 additions and 48 deletions
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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, '&')
|
return text.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, '"')
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue