Skip to content

Conversation

@omgian
Copy link
Collaborator

@omgian omgian commented Jan 14, 2026

one config for all state validation for paths and literal map
redirect invalid paths to parent /vehicles/honda/f150 -> /vehicles/honda
use ButtonsMain component

Gian Aguirre added 8 commits January 13, 2026 15:15
# Conflicts:
#	server/api/sitemap-urls.js
… away from hardcoded, make api calls to google sheets
# Conflicts:
#	data/vehicles.js
#	nuxt.config.ts
#	pages/car-insurance/[state]/index.vue
#	utils/redirect-config.js
…ponent to be used in vehicle make model pages. make call to google sheet api, new images in webp
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements Google Sheets integration for dynamic vehicle and state insurance data management, replacing hardcoded data with API-driven content from Google Sheets. It also restructures vehicle-related URLs from /car-insurance/vehicles to /car-insurance/rates-by-vehicle and creates comprehensive vehicle insurance pages for makes and models.

Changes:

  • Integrated Google Sheets API for state insurance and vehicle data management with server-side caching
  • Created new vehicle insurance pages with three-level navigation (all vehicles → specific make → specific model)
  • Refactored FAQ component to be more generic and reusable across different page types
  • Updated redirect configuration and added new API endpoints for data retrieval

Reviewed changes

Copilot reviewed 21 out of 60 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
nuxt.config.ts Added Google Sheets API configuration with spreadsheet IDs and API key setup
package.json Added googleapis and sharp dependencies for Google Sheets integration and image processing
server/utils/googleSheets.js New utility for fetching Google Sheets data with caching functionality
server/api/sheets/*.js New API endpoints for states, vehicles, vehicle makes, and cache management
views/StateAutoInsurancePage.vue Refactored to fetch state data from Google Sheets instead of hardcoded arrays
pages/car-insurance/rates-by-vehicle/*.vue New vehicle insurance pages with dynamic content from Google Sheets
data/vehicles.js Expanded vehicle data structure and added utility functions for image/logo paths
data/cities.js New file for state/city configuration (foundation for future city pages)
components/Faq/Accordion.vue Refactored to accept FAQ array as prop instead of bundle-specific content
components/BundlePage.vue Updated to work with refactored FAQ component
utils/redirect-config.js Changed vehicle route from "vehicles" to "rates-by-vehicle"
.env.example Added environment variable documentation for Google Sheets API configuration
public/assets/vehicles/* Added vehicle images and logos for Ford and Toyota makes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// gid='0' is the first sheet (Sheet1)
const vehicles = await fetchSheetData(spreadsheetId, range, {
useCache: query.nocache !== 'true',
ttl: 1000 * 60 * 60 * 24 * 30, // 30 days cache for vehicle data
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance: Long cache TTL without invalidation strategy. The vehicle and state data is cached for 30 days (line 33, 38 in vehicles.js, and similar in other endpoints), but there's no automated cache invalidation when the Google Sheets are updated. This means stale data could be served for up to 30 days. Consider implementing a webhook-based cache invalidation strategy or reducing the TTL to a more reasonable duration (e.g., 1 hour to 1 day) to balance performance with data freshness.

Copilot uses AI. Check for mistakes.
Comment on lines +94 to +104
// Get vehicle image path - images stored at /assets/vehicles/{year}/{make}/{model}.webp (or .png fallback)
export function getVehicleImagePath(make, model, year = DEFAULT_VEHICLE_YEAR, format = "webp") {
// Remove hyphens from model slug for image filename (e.g., "bronco-sport" -> "broncosport")
const imageModel = model.replace(/-/g, "");
return `/assets/vehicles/${year}/${make}/${imageModel}.${format}`;
}

// Get make logo path - logos stored at /assets/vehicles/{year}/{make}/logo.webp (or .png fallback)
export function getMakeLogoPath(make, year = DEFAULT_VEHICLE_YEAR, format = "webp") {
return `/assets/vehicles/${year}/${make}/logo.${format}`;
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation: Missing JSDoc documentation for new utility functions. The getVehicleImagePath and getMakeLogoPath functions (lines 95-104) lack documentation explaining their parameters, return values, and usage examples. This is especially important for public utility functions that will be used across multiple components.

Copilot uses AI. Check for mistakes.
Comment on lines +42 to +97
const defaultFaqs = computed(() => {
// Check if spreadsheet has custom FAQs (using faq_question_1 and faq_answer_1 format)
const hasCustomFaqs = makeData.value?.['faq_question_1'] && makeData.value?.['faq_answer_1'];
if (hasCustomFaqs) {
const customFaqs = [
{
question: makeData.value['faq_question_1'],
answer: makeData.value['faq_answer_1'],
}
];
if (makeData.value['faq_question_2'] && makeData.value['faq_answer_2']) {
customFaqs.push({
question: makeData.value['faq_question_2'],
answer: makeData.value['faq_answer_2'],
});
}
if (makeData.value['faq_question_3'] && makeData.value['faq_answer_3']) {
customFaqs.push({
question: makeData.value['faq_question_3'],
answer: makeData.value['faq_answer_3'],
});
}
if (makeData.value['faq_question_4'] && makeData.value['faq_answer_4']) {
customFaqs.push({
question: makeData.value['faq_question_4'],
answer: makeData.value['faq_answer_4'],
});
}
return customFaqs;
}
// Return default FAQs if no custom FAQs in spreadsheet
return [
{
question: `How much does ${formattedMake.value} insurance cost?`,
answer: `${formattedMake.value} insurance costs vary by model. Factors like your driving record, location, age, and coverage selection also significantly impact your premium. Compare quotes to find the best rate.`,
},
{
question: `What factors affect ${formattedMake.value} insurance rates?`,
answer: `Insurance rates are influenced by vehicle-specific factors (safety ratings, repair costs, theft risk, performance) and driver factors (age, driving record, credit score, location). ${formattedMake.value} vehicles with advanced safety features often qualify for discounts.`,
},
{
question: `Are ${formattedMake.value} vehicles expensive to repair?`,
answer: `Repair costs vary by model. Parts availability, labor complexity, and technology features all affect repair expenses. This directly impacts your collision and comprehensive coverage premiums.`,
},
{
question: `How can I save on ${formattedMake.value} insurance?`,
answer: `Compare quotes from multiple insurers, bundle policies, maintain a clean driving record, take advantage of safety feature discounts, consider higher deductibles, and ask about good driver, low mileage, and multi-vehicle discounts.`,
},
];
});
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maintainability: Duplicated FAQ building logic across multiple pages. The pattern of checking for custom FAQs (faq_question_1/faq_answer_1 format) and building an array is duplicated in pages/car-insurance/rates-by-vehicle/[make]/index.vue (lines 42-97) and pages/car-insurance/rates-by-vehicle/[make]/[model].vue (lines 200-264). Consider extracting this logic into a reusable composable or utility function to reduce duplication and make future updates easier.

Copilot uses AI. Check for mistakes.

try {
// Fetch data from Google Sheets Models tab with caching
const models = await fetchSheetData(spreadsheetId, range, {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing import statement for fetchSheetData function. This endpoint uses fetchSheetData but doesn't import it from the googleSheets utility module.

Copilot uses AI. Check for mistakes.

try {
// Fetch data from Google Sheets Makes tab with caching
const makes = await fetchSheetData(spreadsheetId, range, {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing import statement for fetchSheetData function. This endpoint uses fetchSheetData but doesn't import it from the googleSheets utility module.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +36
// Fetch data from Google Sheets with caching
// gid='0' is the first sheet (Sheet1)
const vehicles = await fetchSheetData(spreadsheetId, range, {
useCache: query.nocache !== 'true',
ttl: 1000 * 60 * 60 * 24 * 30, // 30 days cache for vehicle data
headerRow: true,
gid: '0', // First sheet
});
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The gid option is not supported by the fetchSheetData function. This code passes a gid option on line 35 that will be ignored by fetchSheetData.

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +86
// Return single object if filtering by slug, otherwise array
if (query.slug) {
return filteredVehicles[0] || null;
}

return filteredVehicles;
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API design: Inconsistent API response format for single vs multiple results. The vehicles endpoint returns either a single object (when filtering by slug, line 82-83) or an array (line 86). This inconsistency can lead to runtime errors if consumers don't handle both cases. Consider always returning an array for consistency, or document this behavior clearly in API comments.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +60
// Fetch model data from Google Sheets first so we can use it in formattedModel
const { data: modelData } = await useFetch(`/api/sheets/vehicles-detail`, {
query: { make, model },
key: `model-${make}-${model}`,
});
const formattedModel = computed(() => {
// Use the 'model' column from spreadsheet if available, otherwise format the slug
if (modelData.value && modelData.value['model']) {
return modelData.value['model'];
}
return model
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join("-");
});
// Vehicle image path
const vehicleImage = computed(() => getVehicleImagePath(make, model));
const imageError = ref(false);
const onImageError = () => {
imageError.value = true;
};
// Track image errors for other models
const otherModelImageErrors = ref({});
const getOtherModelImage = (modelSlug) => getVehicleImagePath(make, modelSlug);
const onOtherModelImageError = (modelSlug) => {
otherModelImageErrors.value[modelSlug] = true;
};
// Make logo
const makeLogo = computed(() => getMakeLogoPath(make));
const logoError = ref(false);
const onLogoError = () => {
logoError.value = true;
};
// Fetch all models for this make to get display names
const { data: allModelsData } = await useFetch(`/api/sheets/vehicles-detail`, {
query: { make },
key: `all-models-${make}`,
});
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance: Multiple sequential await calls in page setup. The model page fetches data sequentially with three await statements (lines 19, 24, 57), which blocks rendering until all data is loaded. Consider using Promise.all() to fetch modelData, modelsData, and allModelsData concurrently, which would reduce the total page load time.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +22
// Simple token-based authentication for cache clearing
const expectedToken = config.cacheClearToken || config.public.cacheClearToken;

if (!expectedToken || query.token !== expectedToken) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized - Invalid or missing token',
});
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: The cache clear endpoint lacks proper authentication. While there's a token check, if the expectedToken is undefined or empty (when CACHE_CLEAR_TOKEN environment variable is not set), the check on line 17 will pass with an empty query.token, effectively bypassing authentication. Add explicit validation to ensure expectedToken is defined and not empty before performing the comparison.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +41
try {
// Fetch data from Google Sheets Models tab with caching
const models = await fetchSheetData(spreadsheetId, range, {
useCache: query.nocache !== 'true',
ttl: 1000 * 60 * 60 * 24 * 30, // 30 days cache for vehicle model data
headerRow: true,
gid: gid,
});
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The gid option is not supported by the fetchSheetData function. The code passes a gid option on lines 35 and 40, but the fetchSheetData function in googleSheets.js only accepts ttl, useCache, headerRow, and skipRows in its options parameter. This will be silently ignored and could lead to confusion. If you need to support different sheets by gid, consider updating the fetchSheetData function or using the sheet name in the range parameter instead.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants