refactor: remove all any types, add strict GraphQL scalar typing
All checks were successful
Build Docker Image / build (push) Successful in 4m3s
All checks were successful
Build Docker Image / build (push) Successful in 4m3s
- Add strictScalars: true to codegen.ts with proper scalar mappings (Date, Decimal, JSONString, JSON, UUID, BigInt → string/Record) - Replace all ref<any[]> with proper GraphQL-derived types - Add type guards for null filtering in arrays - Fix bugs exposed by typing (locationLatitude vs latitude, etc.) - Add interfaces for external components (MapboxSearchBox) This enables end-to-end type safety from GraphQL schema to frontend.
This commit is contained in:
@@ -118,7 +118,9 @@ import { FormKitSchema } from '@formkit/vue'
|
||||
import type { FormKitSchemaNode } from '@formkit/core'
|
||||
import { GetProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
||||
import { CreateOfferDocument } from '~/composables/graphql/team/exchange-generated'
|
||||
import { GetTeamAddressesDocument } from '~/composables/graphql/team/teams-generated'
|
||||
import { GetTeamAddressesDocument, type GetTeamAddressesQueryResult } from '~/composables/graphql/team/teams-generated'
|
||||
|
||||
type TeamAddress = NonNullable<NonNullable<GetTeamAddressesQueryResult['teamAddresses']>[number]>
|
||||
|
||||
definePageMeta({
|
||||
layout: 'topnav',
|
||||
@@ -147,7 +149,7 @@ const productName = ref<string>('')
|
||||
const schemaId = ref<string | null>(null)
|
||||
const schemaDescription = ref<string | null>(null)
|
||||
const formkitSchema = ref<FormKitSchemaNode[]>([])
|
||||
const addresses = ref<any[]>([])
|
||||
const addresses = ref<TeamAddress[]>([])
|
||||
const selectedAddressUuid = ref<string | null>(null)
|
||||
const formKitConfig = {
|
||||
classes: {
|
||||
@@ -169,8 +171,8 @@ const loadAddresses = async () => {
|
||||
try {
|
||||
const { data, error: addressesError } = await useServerQuery('offer-form-addresses', GetTeamAddressesDocument, {}, 'team', 'teams')
|
||||
if (addressesError.value) throw addressesError.value
|
||||
addresses.value = data.value?.teamAddresses || []
|
||||
const defaultAddress = addresses.value.find((address: any) => address.isDefault)
|
||||
addresses.value = (data.value?.teamAddresses || []).filter((a): a is TeamAddress => a !== null)
|
||||
const defaultAddress = addresses.value.find((address) => address.isDefault)
|
||||
selectedAddressUuid.value = defaultAddress?.uuid || addresses.value[0]?.uuid || null
|
||||
} catch (err) {
|
||||
console.error('Failed to load addresses:', err)
|
||||
@@ -189,7 +191,7 @@ const loadData = async () => {
|
||||
const { data: productsData, error: productsError } = await useServerQuery('offer-form-products', GetProductsDocument, {}, 'public', 'exchange')
|
||||
if (productsError.value) throw productsError.value
|
||||
const products = productsData.value?.getProducts || []
|
||||
const product = products.find((p: any) => p.uuid === productUuid.value)
|
||||
const product = products.find((p) => p?.uuid === productUuid.value)
|
||||
|
||||
if (!product) {
|
||||
throw new Error(t('clientOfferForm.errors.productNotFound', { uuid: productUuid.value }))
|
||||
@@ -219,9 +221,9 @@ const loadData = async () => {
|
||||
formkitSchema.value = schemaToFormKit(terminusClass, enums)
|
||||
await loadAddresses()
|
||||
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
hasError.value = true
|
||||
error.value = err.message || t('clientOfferForm.error.load')
|
||||
error.value = err instanceof Error ? err.message : t('clientOfferForm.error.load')
|
||||
console.error('Load error:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
@@ -237,7 +239,7 @@ const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
throw new Error(t('clientOfferForm.error.load'))
|
||||
}
|
||||
|
||||
const selectedAddress = addresses.value.find((address: any) => address.uuid === selectedAddressUuid.value)
|
||||
const selectedAddress = addresses.value.find((address) => address?.uuid === selectedAddressUuid.value)
|
||||
if (!selectedAddress) {
|
||||
throw new Error(t('clientOfferForm.error.save'))
|
||||
}
|
||||
@@ -253,14 +255,14 @@ const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
locationCountryCode: selectedAddress.countryCode || '',
|
||||
locationLatitude: selectedAddress.latitude,
|
||||
locationLongitude: selectedAddress.longitude,
|
||||
quantity: data.quantity || 0,
|
||||
quantity: String(data.quantity || '0'),
|
||||
unit: String(data.unit || 'ton'),
|
||||
pricePerUnit: data.price_per_unit || data.pricePerUnit || null,
|
||||
pricePerUnit: String(data.price_per_unit || data.pricePerUnit || ''),
|
||||
currency: String(data.currency || 'USD'),
|
||||
description: String(data.description || ''),
|
||||
validUntil: data.valid_until || data.validUntil || null,
|
||||
validUntil: (data.valid_until as string | undefined) ?? (data.validUntil as string | undefined) ?? undefined,
|
||||
terminusSchemaId: schemaId.value,
|
||||
terminusPayload: JSON.stringify(data),
|
||||
terminusPayload: data,
|
||||
}
|
||||
|
||||
const result = await mutate(CreateOfferDocument, { input }, 'team', 'exchange')
|
||||
@@ -270,8 +272,8 @@ const handleSubmit = async (data: Record<string, unknown>) => {
|
||||
|
||||
await navigateTo(localePath('/clientarea/offers'))
|
||||
|
||||
} catch (err: any) {
|
||||
error.value = err.message || t('clientOfferForm.error.save')
|
||||
} catch (err: unknown) {
|
||||
error.value = err instanceof Error ? err.message : t('clientOfferForm.error.save')
|
||||
hasError.value = true
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
|
||||
@@ -122,7 +122,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MapBounds } from '~/components/catalog/CatalogMap.vue'
|
||||
import { GetOffersDocument } from '~/composables/graphql/public/exchange-generated'
|
||||
import { GetOffersDocument, type GetOffersQueryResult } from '~/composables/graphql/public/exchange-generated'
|
||||
|
||||
type Offer = NonNullable<NonNullable<GetOffersQueryResult['getOffers']>[number]>
|
||||
|
||||
definePageMeta({
|
||||
layout: 'topnav',
|
||||
@@ -135,7 +137,7 @@ const { activeTeamId } = useActiveTeam()
|
||||
|
||||
const { execute } = useGraphQL()
|
||||
const PAGE_SIZE = 24
|
||||
const offers = ref<any[]>([])
|
||||
const offers = ref<Offer[]>([])
|
||||
const totalOffers = ref(0)
|
||||
const isLoadingMore = ref(false)
|
||||
|
||||
@@ -164,7 +166,7 @@ const {
|
||||
|
||||
watchEffect(() => {
|
||||
if (offersData.value?.getOffers) {
|
||||
offers.value = offersData.value.getOffers
|
||||
offers.value = offersData.value.getOffers.filter((o): o is Offer => o !== null)
|
||||
totalOffers.value = offersData.value.getOffersCount ?? offersData.value.getOffers.length
|
||||
}
|
||||
})
|
||||
@@ -231,9 +233,11 @@ const onSearch = () => {
|
||||
// TODO: Implement search
|
||||
}
|
||||
|
||||
const onSelectOffer = (offer: any) => {
|
||||
selectedOfferId.value = offer.uuid
|
||||
navigateTo(localePath(`/clientarea/offers/${offer.uuid}`))
|
||||
const onSelectOffer = (offer: { uuid?: string | null }) => {
|
||||
if (offer.uuid) {
|
||||
selectedOfferId.value = offer.uuid
|
||||
navigateTo(localePath(`/clientarea/offers/${offer.uuid}`))
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusVariant = (status: string) => {
|
||||
@@ -293,7 +297,7 @@ const fetchOffers = async (offset = 0, replace = false) => {
|
||||
'public',
|
||||
'exchange'
|
||||
)
|
||||
const next = data?.getOffers || []
|
||||
const next = (data?.getOffers || []).filter((o): o is Offer => o !== null)
|
||||
offers.value = replace ? next : offers.value.concat(next)
|
||||
totalOffers.value = data?.getOffersCount ?? totalOffers.value
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<template v-else>
|
||||
<Grid v-if="products.length" :cols="1" :md="2" :lg="3" :gap="4">
|
||||
<Card
|
||||
v-for="product in products"
|
||||
:key="product.uuid"
|
||||
v-for="(product, index) in products"
|
||||
:key="product.uuid ?? index"
|
||||
padding="lg"
|
||||
class="cursor-pointer hover:shadow-md transition-shadow"
|
||||
@click="selectProduct(product)"
|
||||
@@ -51,7 +51,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { GetProductsDocument } from '~/composables/graphql/public/exchange-generated'
|
||||
import { GetProductsDocument, type GetProductsQueryResult } from '~/composables/graphql/public/exchange-generated'
|
||||
|
||||
type Product = NonNullable<NonNullable<GetProductsQueryResult['getProducts']>[number]>
|
||||
|
||||
definePageMeta({
|
||||
layout: 'topnav',
|
||||
@@ -62,7 +64,7 @@ const localePath = useLocalePath()
|
||||
const { t } = useI18n()
|
||||
const { execute } = useGraphQL()
|
||||
|
||||
const products = ref<any[]>([])
|
||||
const products = ref<Product[]>([])
|
||||
const isLoading = ref(true)
|
||||
const hasError = ref(false)
|
||||
const error = ref('')
|
||||
@@ -73,19 +75,19 @@ const loadProducts = async () => {
|
||||
hasError.value = false
|
||||
const { data, error: productsError } = await useServerQuery('offers-new-products', GetProductsDocument, {}, 'public', 'exchange')
|
||||
if (productsError.value) throw productsError.value
|
||||
products.value = data.value?.getProducts || []
|
||||
} catch (err: any) {
|
||||
products.value = (data.value?.getProducts || []).filter((p): p is Product => p !== null)
|
||||
} catch (err: unknown) {
|
||||
hasError.value = true
|
||||
error.value = err.message || t('offersNew.errors.load_failed')
|
||||
error.value = err instanceof Error ? err.message : t('offersNew.errors.load_failed')
|
||||
products.value = []
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const selectProduct = (product: any) => {
|
||||
const selectProduct = (product: { uuid?: string | null }) => {
|
||||
// Navigate to product details page
|
||||
navigateTo(localePath(`/clientarea/offers/${product.uuid}`))
|
||||
if (product.uuid) navigateTo(localePath(`/clientarea/offers/${product.uuid}`))
|
||||
}
|
||||
|
||||
await loadProducts()
|
||||
|
||||
Reference in New Issue
Block a user