Skip to content

make CRUD for spacex#91

Open
alfinforwork wants to merge 1 commit into
allobankdev:mainfrom
alfinforwork:feat/allo-spacex
Open

make CRUD for spacex#91
alfinforwork wants to merge 1 commit into
allobankdev:mainfrom
alfinforwork:feat/allo-spacex

Conversation

@alfinforwork
Copy link
Copy Markdown

  • list of rockets (Show each rocket image, rocket name, and rocket description)
  • filter the rockets
  • add the new rocket
  • the rocket detail (Show rocket image, rocket name, rocket description, cost per launch, country, first flight)

Copilot AI review requested due to automatic review settings January 28, 2026 03:10
Copy link
Copy Markdown

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 a comprehensive CRUD interface for SpaceX rockets, adding functionality to list, filter, view details, and create custom rockets. The implementation uses Vue 3 with Vuetify for UI components, Pinia for state management, and integrates with the SpaceX API.

Changes:

  • Added complete state management with Pinia store for rocket data and custom rockets
  • Implemented SpaceX API service layer with error handling for fetching rocket data
  • Created responsive UI components for rocket list, detail views, and add rocket modal with Vuetify

Reviewed changes

Copilot reviewed 15 out of 19 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
src/stores/rocketStore.ts Pinia store managing rocket state, filtering, and CRUD operations
src/services/spaceXApi.ts API service layer for fetching rocket data from SpaceX API
src/router/index.ts Updated router configuration to support auto-routes from unplugin-vue-router
src/plugins/index.ts Added Pinia plugin registration
src/pages/index.vue Updated home page to display RocketList component
src/pages/rocket/[id].vue Dynamic route page for rocket detail view with error and loading states
src/components/RocketList/RocketList.vue Main list component with filtering, grid layout, and navigation
src/components/RocketList/AddRocketModal.vue Modal form for creating custom rockets
src/components/RocketDetail/RocketDetailHeader.vue Header component displaying rocket image and basic info
src/components/RocketDetail/RocketDetailInfo.vue Component displaying detailed rocket specifications
src/components/RocketDetail/RocketGallery.vue Gallery component for displaying multiple rocket images
package.json Added Pinia dependency
typed-router.d.ts Generated TypeScript definitions for auto-routes
components.d.ts Generated component type definitions

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

Comment on lines +104 to +114
const rocket: Rocket = {
id: `custom-${Date.now()}`,
name: form.value.name,
type: form.value.type,
description: form.value.description,
cost_per_launch: form.value.cost_per_launch,
country: form.value.country,
first_flight: form.value.first_flight,
rocket_id: `custom-${Date.now()}`,
flickr_images: form.value.imageUrl ? [form.value.imageUrl] : [],
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The id and rocket_id fields are being set in the modal (lines 105, 112) and then overwritten in the store's addRocket function (lines 92-93 of rocketStore.ts). This is redundant and creates unnecessary work. Either generate the IDs only in the store, or only in the modal, but not in both places.

Suggested change
const rocket: Rocket = {
id: `custom-${Date.now()}`,
name: form.value.name,
type: form.value.type,
description: form.value.description,
cost_per_launch: form.value.cost_per_launch,
country: form.value.country,
first_flight: form.value.first_flight,
rocket_id: `custom-${Date.now()}`,
flickr_images: form.value.imageUrl ? [form.value.imageUrl] : [],
}
const rocket = {
name: form.value.name,
type: form.value.type,
description: form.value.description,
cost_per_launch: form.value.cost_per_launch,
country: form.value.country,
first_flight: form.value.first_flight,
flickr_images: form.value.imageUrl ? [form.value.imageUrl] : [],
} as Rocket

Copilot uses AI. Check for mistakes.
Comment on lines +127 to +129
watch(searchQuery, (newValue) => {
rocketStore.setFilterQuery(newValue)
})
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The watch function updates filterQuery immediately on every keystroke. For better performance, especially with large datasets, consider using a debounced watch or watchDebounced to avoid filtering the list on every character input. This will reduce unnecessary computations and improve the user experience.

Copilot uses AI. Check for mistakes.
Comment thread src/stores/rocketStore.ts

export const useRocketStore = defineStore('rocket', () => {
const rockets = ref<Rocket[]>([])
const customRockets = ref<Rocket[]>([])
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Custom rockets added via the modal are stored only in memory and will be lost when the page is refreshed or the user navigates away. Consider persisting customRockets to localStorage or providing clear user feedback that these rockets are temporary. This could lead to user confusion when their added rockets disappear.

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +101
<div
v-for="rocket in rocketStore.allRockets"
:key="rocket.id"
class="rocket-card"
@click="goToDetail(rocket.id)"
>
<div class="rocket-image">
<img
v-if="rocket.flickr_images && rocket.flickr_images.length > 0"
:src="rocket.flickr_images[0]"
:alt="rocket.name"
class="image"
>
<div
v-else
class="placeholder"
>
<v-icon size="48">
mdi-rocket
</v-icon>
</div>
</div>
<div class="rocket-info">
<h2 class="rocket-name">
{{ rocket.name }}
</h2>
<p class="rocket-description">
{{ rocket.description }}
</p>
<div class="rocket-meta">
<span class="badge">{{ rocket.type }}</span>
<span class="badge">{{ rocket.country }}</span>
</div>
</div>
</div>
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The rocket cards are clickable but lack proper keyboard accessibility. Users navigating with keyboard cannot tab to these cards or activate them with Enter/Space keys. Consider making the cards proper interactive elements by using button elements or adding tabindex="0" and @keypress handlers to support keyboard navigation, which is important for accessibility compliance.

Copilot uses AI. Check for mistakes.
Comment thread src/stores/rocketStore.ts
Comment on lines +90 to +93
customRockets.value.push({
...rocket,
id: `custom-${Date.now()}`,
rocket_id: `custom-${Date.now()}`,
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The addRocket function generates duplicate IDs for 'id' and 'rocket_id' using Date.now() in the same call. While unlikely in normal usage, if the function executes quickly enough (which can happen in some JavaScript engines), both calls to Date.now() could return the same value. More importantly, when a rocket is passed in with these fields already set (lines 104-114 in AddRocketModal.vue), they get overwritten here, creating a discrepancy between what the modal created and what gets stored.

Suggested change
customRockets.value.push({
...rocket,
id: `custom-${Date.now()}`,
rocket_id: `custom-${Date.now()}`,
const generatedId = `custom-${Date.now()}`
const finalId = rocket.id || rocket.rocket_id || generatedId
const finalRocketId = rocket.rocket_id || finalId
customRockets.value.push({
...rocket,
id: finalId,
rocket_id: finalRocketId,

Copilot uses AI. Check for mistakes.
Comment thread src/pages/rocket/[id].vue
Comment on lines +42 to +50
<div
v-else-if="rocketStore.selectedRocket"
class="detail-content"
>
<RocketDetailHeader :rocket="rocketStore.selectedRocket" />
<RocketDetailInfo :rocket="rocketStore.selectedRocket" />
<RocketGallery :images="rocketStore.selectedRocket.flickr_images" />
</div>
</div>
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The component doesn't handle the case where rocketId is undefined or when the rocket is not found after loading (selectedRocket is null but no error). After loadRocketDetail completes successfully, if selectedRocket is still null, the UI will show nothing. Consider adding an else clause to handle this case with a "Rocket not found" message.

Copilot uses AI. Check for mistakes.
height: 200px;
object-fit: cover;
border-radius: 8px;
cursor: pointer;
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Gallery images are clickable (cursor: pointer) but have no click handler or keyboard interaction. This creates a misleading UX where users expect something to happen on click. Either remove the cursor pointer styling or implement functionality like opening images in a lightbox/modal for a better viewing experience.

Suggested change
cursor: pointer;

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +9
<img
v-if="rocket.flickr_images && rocket.flickr_images.length > 0"
:src="rocket.flickr_images[0]"
:alt="rocket.name"
class="detail-image"
>
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Images loaded from external URLs (Flickr) have no error handling. If an image fails to load, it will show a broken image icon. Consider adding an @error handler to the img tag to fall back to the placeholder icon when image loading fails, providing a better user experience.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +102
if (!form.value.name || !form.value.description) {
alert('Please fill in all required fields')
return
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Using alert() for form validation feedback is not consistent with the rest of the application which uses Vuetify components like v-alert. Consider using a v-snackbar or inline validation error messages for better user experience and consistency with the UI framework.

Copilot uses AI. Check for mistakes.
Comment on lines +99 to +113
if (!form.value.name || !form.value.description) {
alert('Please fill in all required fields')
return
}

const rocket: Rocket = {
id: `custom-${Date.now()}`,
name: form.value.name,
type: form.value.type,
description: form.value.description,
cost_per_launch: form.value.cost_per_launch,
country: form.value.country,
first_flight: form.value.first_flight,
rocket_id: `custom-${Date.now()}`,
flickr_images: form.value.imageUrl ? [form.value.imageUrl] : [],
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Form validation is incomplete. The validation only checks for name and description, but the form fields for type, country, and first_flight are marked as 'required' in the template (lines 14, 20, 40, 46). This creates an inconsistency between the HTML required attributes and the actual validation logic, which could confuse users and allow invalid data to be submitted.

Suggested change
if (!form.value.name || !form.value.description) {
alert('Please fill in all required fields')
return
}
const rocket: Rocket = {
id: `custom-${Date.now()}`,
name: form.value.name,
type: form.value.type,
description: form.value.description,
cost_per_launch: form.value.cost_per_launch,
country: form.value.country,
first_flight: form.value.first_flight,
rocket_id: `custom-${Date.now()}`,
flickr_images: form.value.imageUrl ? [form.value.imageUrl] : [],
const {
name,
type,
description,
cost_per_launch,
country,
first_flight,
imageUrl,
} = form.value
if (
!name ||
!type ||
!description ||
cost_per_launch === null ||
cost_per_launch === undefined ||
!country ||
!first_flight
) {
alert('Please fill in all required fields')
return
}
const rocket: Rocket = {
id: `custom-${Date.now()}`,
name,
type,
description,
cost_per_launch,
country,
first_flight,
rocket_id: `custom-${Date.now()}`,
flickr_images: imageUrl ? [imageUrl] : [],

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants