-
Notifications
You must be signed in to change notification settings - Fork 2
Dsn 1625 auto make model pages #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: stage
Are you sure you want to change the base?
Conversation
…ient side. add vehicle make model files
# Conflicts: # server/api/sitemap-urls.js
…ndpoints, adjust site mapping
… 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
There was a problem hiding this 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 |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| // 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}`; | ||
| } |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| 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.`, | ||
| }, | ||
| ]; | ||
| }); |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
|
|
||
| try { | ||
| // Fetch data from Google Sheets Models tab with caching | ||
| const models = await fetchSheetData(spreadsheetId, range, { |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
|
|
||
| try { | ||
| // Fetch data from Google Sheets Makes tab with caching | ||
| const makes = await fetchSheetData(spreadsheetId, range, { |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| // 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 | ||
| }); |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| // Return single object if filtering by slug, otherwise array | ||
| if (query.slug) { | ||
| return filteredVehicles[0] || null; | ||
| } | ||
|
|
||
| return filteredVehicles; |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| // 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}`, | ||
| }); |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| // 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', | ||
| }); | ||
| } |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
| 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, | ||
| }); |
Copilot
AI
Jan 23, 2026
There was a problem hiding this comment.
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.
…- again, add disclaimer for the rates
one config for all state validation for paths and literal map
redirect invalid paths to parent /vehicles/honda/f150 -> /vehicles/honda
use ButtonsMain component