fix: resolve CI and workspace lint errors (admin + storefront)
Some checks failed
CI / Lint, Typecheck & Test (push) Failing after 2m11s

- Allow require() in next.config.js (eslint-disable) for both apps
- Replace all catch (e: any) with catch (e: unknown) and proper error handling
- Remove no-explicit-any: add types (PreviewProduct, ProductImage, Id<addresses>,
  ProductDetailReview, error payloads) across admin and storefront
- Admin: use next/image in ImageUploadSection and ProductImageCarousel; remove
  unused layout fonts and sidebar imports; fix products page useMemo deps
- Storefront: use Link for /sign-in in header actions; fix useAddressMutations
  and product detail types; remove unused imports/vars and fix useMemo deps

Made-with: Cursor
This commit is contained in:
2026-03-08 00:45:57 +03:00
parent 2f5537cf7e
commit 23efcab80c
34 changed files with 87 additions and 89 deletions

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-require-imports -- Next.js config commonly uses require */
const path = require("path"); const path = require("path");
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */

View File

@@ -5,7 +5,10 @@ import { useQuery } from "convex/react"
import { api } from "../../../../../../convex/_generated/api" import { api } from "../../../../../../convex/_generated/api"
import type { Id } from "../../../../../../convex/_generated/dataModel" import type { Id } from "../../../../../../convex/_generated/dataModel"
import { ProductSearchSection } from "../../../components/shared/ProductSearchSection" import { ProductSearchSection } from "../../../components/shared/ProductSearchSection"
import { ProductImageCarousel } from "../../../components/images/ProductImageCarousel" import {
ProductImageCarousel,
type ProductImage,
} from "../../../components/images/ProductImageCarousel"
import { ImageUploadSection } from "../../../components/images/ImageUploadSection" import { ImageUploadSection } from "../../../components/images/ImageUploadSection"
import { Skeleton } from "@/components/ui/skeleton" import { Skeleton } from "@/components/ui/skeleton"
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator"
@@ -79,7 +82,7 @@ export default function ImagesPage() {
</div> </div>
) : ( ) : (
<ProductImageCarousel <ProductImageCarousel
images={images as any} images={images as ProductImage[]}
onAddMore={() => setShowUpload(true)} onAddMore={() => setShowUpload(true)}
/> />
)} )}

View File

@@ -53,8 +53,8 @@ export default function EditProductPage() {
categoryId: payload.categoryId as Id<"categories">, categoryId: payload.categoryId as Id<"categories">,
}) })
router.push("/products") router.push("/products")
} catch (e: any) { } catch (e: unknown) {
setError(e?.message ?? "Failed to save product. Please try again.") setError(e instanceof Error ? e.message : "Failed to save product. Please try again.")
setIsSubmitting(false) setIsSubmitting(false)
} }
} }
@@ -64,8 +64,8 @@ export default function EditProductPage() {
try { try {
await archiveProduct({ id: productId }) await archiveProduct({ id: productId })
router.push("/products") router.push("/products")
} catch (e: any) { } catch (e: unknown) {
setError(e?.message ?? "Failed to archive product.") setError(e instanceof Error ? e.message : "Failed to archive product.")
setIsArchiving(false) setIsArchiving(false)
} }
} }

View File

@@ -33,8 +33,8 @@ export default function NewProductPage() {
categoryId: payload.categoryId as Id<"categories">, categoryId: payload.categoryId as Id<"categories">,
}) })
router.push("/products") router.push("/products")
} catch (e: any) { } catch (e: unknown) {
setError(e?.message ?? "Failed to create product. Please try again.") setError(e instanceof Error ? e.message : "Failed to create product. Please try again.")
setIsSubmitting(false) setIsSubmitting(false)
} }
} }

View File

@@ -199,11 +199,14 @@ export default function ProductsPage() {
? searchResults === undefined ? searchResults === undefined
: listStatus === "LoadingFirstPage" : listStatus === "LoadingFirstPage"
const rawProducts = isSearching ? (searchResults ?? []) : listResults const rawProducts = useMemo(
() => (isSearching ? (searchResults ?? []) : listResults),
[isSearching, searchResults, listResults],
)
const products = useMemo(() => { const products = useMemo(() => {
if (!sortField) return rawProducts if (!sortField) return rawProducts
return [...rawProducts].sort((a: any, b: any) => { return [...rawProducts].sort((a: PreviewProduct, b: PreviewProduct) => {
const aVal: string = const aVal: string =
sortField === "name" sortField === "name"
? (a.name ?? "") ? (a.name ?? "")
@@ -235,8 +238,8 @@ export default function ProductsPage() {
setVisibleCols((prev) => ({ ...prev, [key]: checked })) setVisibleCols((prev) => ({ ...prev, [key]: checked }))
} }
function openPreview(product: any) { function openPreview(product: PreviewProduct) {
setPreviewProduct(product as PreviewProduct) setPreviewProduct(product)
setPreviewOpen(true) setPreviewOpen(true)
} }
@@ -374,7 +377,7 @@ export default function ProductsPage() {
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
products.map((product: any) => { products.map((product: PreviewProduct) => {
const statusCfg = const statusCfg =
STATUS_CONFIG[product.status as keyof typeof STATUS_CONFIG] STATUS_CONFIG[product.status as keyof typeof STATUS_CONFIG]
return ( return (

View File

@@ -1,21 +1,11 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { DM_Sans, Geist, Geist_Mono } from "next/font/google"; import { DM_Sans } from "next/font/google";
import { ClerkProvider } from "@clerk/nextjs"; import { ClerkProvider } from "@clerk/nextjs";
import { ConvexClientProvider } from "@repo/convex"; import { ConvexClientProvider } from "@repo/convex";
import { Toaster } from "sonner"; import { Toaster } from "sonner";
import "./globals.css"; import "./globals.css";
const dmSans = DM_Sans({subsets:['latin'],variable:'--font-sans'}); const dmSans = DM_Sans({ subsets: ["latin"], variable: "--font-sans" });
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: { title: {

View File

@@ -1,6 +1,7 @@
"use client" "use client"
import { useState, useRef } from "react" import { useState, useRef } from "react"
import Image from "next/image"
import { useMutation } from "convex/react" import { useMutation } from "convex/react"
import { api } from "../../../../../convex/_generated/api" import { api } from "../../../../../convex/_generated/api"
import type { Id } from "../../../../../convex/_generated/dataModel" import type { Id } from "../../../../../convex/_generated/dataModel"
@@ -61,8 +62,8 @@ export function ImageUploadSection({
}) })
if (!res.ok) { if (!res.ok) {
const errJson = await res.json().catch(() => ({})) const errJson = (await res.json().catch(() => ({}))) as { detail?: string }
throw new Error((errJson as any).detail ?? "Background removal failed") throw new Error(errJson.detail ?? "Background removal failed")
} }
const blob = await res.blob() const blob = await res.blob()
@@ -100,8 +101,8 @@ export function ImageUploadSection({
}) })
if (!uploadRes.ok) { if (!uploadRes.ok) {
const errJson = await uploadRes.json().catch(() => ({})) const errJson = (await uploadRes.json().catch(() => ({}))) as { error?: string }
throw new Error((errJson as any).error ?? "Upload failed") throw new Error(errJson.error ?? "Upload failed")
} }
const { url } = await uploadRes.json() const { url } = await uploadRes.json()
@@ -188,8 +189,14 @@ export function ImageUploadSection({
<div className="space-y-1"> <div className="space-y-1">
<p className="text-xs font-medium text-muted-foreground">Original</p> <p className="text-xs font-medium text-muted-foreground">Original</p>
{localUrl && ( {localUrl && (
<div className="aspect-square overflow-hidden rounded-md border bg-muted"> <div className="relative aspect-square overflow-hidden rounded-md border bg-muted">
<img src={localUrl} alt="Original" className="h-full w-full object-contain" /> <Image
src={localUrl}
alt="Original"
fill
className="object-contain"
unoptimized
/>
</div> </div>
)} )}
</div> </div>
@@ -199,11 +206,13 @@ export function ImageUploadSection({
{isProcessing ? ( {isProcessing ? (
<Skeleton className="aspect-square w-full rounded-md" /> <Skeleton className="aspect-square w-full rounded-md" />
) : processedUrl ? ( ) : processedUrl ? (
<div className="aspect-square overflow-hidden rounded-md border bg-[conic-gradient(#e5e7eb_25%,_#fff_25%,_#fff_50%,_#e5e7eb_50%,_#e5e7eb_75%,_#fff_75%)] bg-[length:16px_16px]"> <div className="relative aspect-square overflow-hidden rounded-md border bg-[conic-gradient(#e5e7eb_25%,_#fff_25%,_#fff_50%,_#e5e7eb_50%,_#e5e7eb_75%,_#fff_75%)] bg-[length:16px_16px]">
<img <Image
src={processedUrl} src={processedUrl}
alt="Background removed" alt="Background removed"
className="h-full w-full object-contain" fill
className="object-contain"
unoptimized
/> />
</div> </div>
) : processingError ? ( ) : processingError ? (

View File

@@ -1,6 +1,7 @@
"use client" "use client"
import { useState, useEffect } from "react" import { useState, useEffect } from "react"
import Image from "next/image"
import { import {
DndContext, DndContext,
closestCenter, closestCenter,
@@ -35,7 +36,7 @@ import { Button } from "@/components/ui/button"
import { HugeiconsIcon } from "@hugeicons/react" import { HugeiconsIcon } from "@hugeicons/react"
import { Delete02Icon, ImageAdd01Icon, DragDropVerticalIcon } from "@hugeicons/core-free-icons" import { Delete02Icon, ImageAdd01Icon, DragDropVerticalIcon } from "@hugeicons/core-free-icons"
interface ProductImage { export interface ProductImage {
_id: Id<"productImages"> _id: Id<"productImages">
url: string url: string
alt?: string alt?: string
@@ -105,11 +106,12 @@ function SortableImageCard({
{/* Row 2 in DOM → Row 2 visually (middle stays middle) */} {/* Row 2 in DOM → Row 2 visually (middle stays middle) */}
{/* Image — appears in CENTER */} {/* Image — appears in CENTER */}
<div className="w-28 aspect-square rotate-180 overflow-hidden rounded-md border bg-muted"> <div className="relative w-28 aspect-square rotate-180 overflow-hidden rounded-md border bg-muted">
<img <Image
src={image.url} src={image.url}
alt={image.alt ?? "Product image"} alt={image.alt ?? "Product image"}
className="h-full w-full object-contain" fill
className="object-contain"
/> />
</div> </div>

View File

@@ -84,7 +84,7 @@ export function ProductSearchSection({ onSelect, onClear, selectedId }: ProductS
{!isLoading && results && results.length > 0 && ( {!isLoading && results && results.length > 0 && (
<ul className="max-w-sm divide-y rounded-md border"> <ul className="max-w-sm divide-y rounded-md border">
{results.map((product: any) => ( {results.map((product: { _id: string; name: string }) => (
<li key={product._id}> <li key={product._id}>
<button <button
type="button" type="button"

View File

@@ -1,8 +1,6 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { Store01Icon } from "@hugeicons/core-free-icons";
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,

View File

@@ -24,8 +24,8 @@ export function AcceptReturnButton({ orderId }: Props) {
} else { } else {
toast.error((result as { success: false; code: string; message: string }).message) toast.error((result as { success: false; code: string; message: string }).message)
} }
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to accept return.") toast.error(e instanceof Error ? e.message : "Failed to accept return.")
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }

View File

@@ -24,8 +24,8 @@ export function CreateLabelButton({ orderId }: Props) {
} else { } else {
toast.error((result as { success: false; code: string; message: string }).message) toast.error((result as { success: false; code: string; message: string }).message)
} }
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to create shipping label.") toast.error(e instanceof Error ? e.message : "Failed to create shipping label.")
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }

View File

@@ -37,8 +37,8 @@ export function IssueRefundButton({ orderId, total, currency }: Props) {
await issueRefund({ orderId }) await issueRefund({ orderId })
toast.success("Refund issued.") toast.success("Refund issued.")
setOpen(false) setOpen(false)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to issue refund.") toast.error(e instanceof Error ? e.message : "Failed to issue refund.")
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }

View File

@@ -32,8 +32,8 @@ export function MarkReturnReceivedButton({ orderId }: Props) {
await markReceived({ id: orderId }) await markReceived({ id: orderId })
toast.success("Return marked as received.") toast.success("Return marked as received.")
setOpen(false) setOpen(false)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to mark return as received.") toast.error(e instanceof Error ? e.message : "Failed to mark return as received.")
} finally { } finally {
setIsLoading(false) setIsLoading(false)
} }

View File

@@ -62,8 +62,8 @@ export function UpdateStatusDialog({ orderId, currentStatus }: Props) {
const label = ORDER_STATUS_CONFIG[selectedStatus as OrderStatus]?.label const label = ORDER_STATUS_CONFIG[selectedStatus as OrderStatus]?.label
toast.success(`Status updated to ${label ?? selectedStatus}`) toast.success(`Status updated to ${label ?? selectedStatus}`)
setOpen(false) setOpen(false)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to update status.") toast.error(e instanceof Error ? e.message : "Failed to update status.")
} finally { } finally {
setIsSubmitting(false) setIsSubmitting(false)
} }

View File

@@ -84,7 +84,7 @@ export function ProductSearchSection({ onSelect, onClear, selectedId }: ProductS
{!isLoading && results && results.length > 0 && ( {!isLoading && results && results.length > 0 && (
<ul className="max-w-sm divide-y rounded-md border"> <ul className="max-w-sm divide-y rounded-md border">
{results.map((product: any) => ( {results.map((product: { _id: string; name: string }) => (
<li key={product._id}> <li key={product._id}>
<button <button
type="button" type="button"

View File

@@ -68,8 +68,8 @@ export function CreateVariantDialog({
toast.success(`Variant "${values.name}" created`) toast.success(`Variant "${values.name}" created`)
onOpenChange(false) onOpenChange(false)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to create variant") toast.error(e instanceof Error ? e.message : "Failed to create variant")
} finally { } finally {
setIsSubmitting(false) setIsSubmitting(false)
} }

View File

@@ -69,8 +69,8 @@ export function EditVariantDialog({
toast.success(`"${values.name}" updated`) toast.success(`"${values.name}" updated`)
onOpenChange(false) onOpenChange(false)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to update variant") toast.error(e instanceof Error ? e.message : "Failed to update variant")
} finally { } finally {
setIsSubmitting(false) setIsSubmitting(false)
} }

View File

@@ -103,8 +103,8 @@ function VariantActionsMenu({
try { try {
await updateVariant({ id: variant._id, isActive: !variant.isActive }) await updateVariant({ id: variant._id, isActive: !variant.isActive })
toast.success(`"${variant.name}" ${variant.isActive ? "deactivated" : "activated"}`) toast.success(`"${variant.name}" ${variant.isActive ? "deactivated" : "activated"}`)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to update variant") toast.error(e instanceof Error ? e.message : "Failed to update variant")
} finally { } finally {
setIsToggling(false) setIsToggling(false)
} }
@@ -116,8 +116,8 @@ function VariantActionsMenu({
await deleteVariant({ id: variant._id }) await deleteVariant({ id: variant._id })
toast.success(`"${variant.name}" deleted`) toast.success(`"${variant.name}" deleted`)
setDeleteOpen(false) setDeleteOpen(false)
} catch (e: any) { } catch (e: unknown) {
toast.error(e?.message ?? "Failed to delete variant") toast.error(e instanceof Error ? e.message : "Failed to delete variant")
} finally { } finally {
setIsDeleting(false) setIsDeleting(false)
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-require-imports -- Next.js config commonly uses require */
const path = require("path"); const path = require("path");
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */

View File

@@ -1,6 +1,6 @@
"use client"; "use client";
import { Button, Chip, RadioGroup, Radio, Label, Skeleton } from "@heroui/react"; import { Button, Chip, RadioGroup, Radio, Skeleton } from "@heroui/react";
import type { CheckoutAddress } from "@/lib/checkout/types"; import type { CheckoutAddress } from "@/lib/checkout/types";
type AddressSelectorProps = { type AddressSelectorProps = {

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import Link from "next/link";
import { useConvexAuth } from "convex/react"; import { useConvexAuth } from "convex/react";
import { UserButton } from "@clerk/nextjs"; import { UserButton } from "@clerk/nextjs";
@@ -27,7 +28,7 @@ export function HeaderUserAction() {
if (isLoading || !isAuthenticated) { if (isLoading || !isAuthenticated) {
return ( return (
<a <Link
href="/sign-in" href="/sign-in"
className="group flex flex-col items-center gap-1" className="group flex flex-col items-center gap-1"
> >
@@ -48,7 +49,7 @@ export function HeaderUserAction() {
<span className="text-[10px] font-medium tracking-wide text-[#3d5554] transition-colors group-hover:text-[#236f6b]"> <span className="text-[10px] font-medium tracking-wide text-[#3d5554] transition-colors group-hover:text-[#236f6b]">
Sign In Sign In
</span> </span>
</a> </Link>
); );
} }

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import Link from "next/link";
import { useConvexAuth } from "convex/react"; import { useConvexAuth } from "convex/react";
import { UserButton } from "@clerk/nextjs"; import { UserButton } from "@clerk/nextjs";
@@ -27,7 +28,7 @@ export function MobileHeaderUserAction() {
if (isLoading || !isAuthenticated) { if (isLoading || !isAuthenticated) {
return ( return (
<a <Link
href="/sign-in" href="/sign-in"
className="flex h-8 w-8 items-center justify-center rounded-full border border-[#d9e8e7] bg-[#f9fcfb]" className="flex h-8 w-8 items-center justify-center rounded-full border border-[#d9e8e7] bg-[#f9fcfb]"
> >
@@ -45,7 +46,7 @@ export function MobileHeaderUserAction() {
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /> <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" /> <circle cx="12" cy="7" r="4" />
</svg> </svg>
</a> </Link>
); );
} }

View File

@@ -4,7 +4,7 @@ import { useState } from "react";
import { useQuery } from "convex/react"; import { useQuery } from "convex/react";
import { api } from "../../../../../../convex/_generated/api"; import { api } from "../../../../../../convex/_generated/api";
import type { Id } from "../../../../../../convex/_generated/dataModel"; import type { Id } from "../../../../../../convex/_generated/dataModel";
import { ReviewSortOption } from "@/lib/product-detail/types"; import type { ProductDetailReview, ReviewSortOption } from "@/lib/product-detail/types";
import { ReviewSortBar } from "./ReviewSortBar"; import { ReviewSortBar } from "./ReviewSortBar";
import { ReviewList } from "./ReviewList"; import { ReviewList } from "./ReviewList";
import { ReviewForm } from "./ReviewForm"; import { ReviewForm } from "./ReviewForm";
@@ -58,7 +58,7 @@ export function ProductDetailReviewsPanel({ productId, initialRating, initialRev
onSortChange={handleSortChange} onSortChange={handleSortChange}
/> />
<ReviewList <ReviewList
reviews={page as any} reviews={page as ProductDetailReview[]}
total={total} total={total}
hasMore={hasMore} hasMore={hasMore}
isLoading={result === undefined} isLoading={result === undefined}

View File

@@ -206,7 +206,10 @@ export function ProductDetailHeroSection({
const [addError, setAddError] = useState<string | null>(null); const [addError, setAddError] = useState<string | null>(null);
const images: ProductDetailImage[] = product.images ?? []; const images: ProductDetailImage[] = product.images ?? [];
const variants: ProductDetailVariant[] = product.variants ?? []; const variants = useMemo(
() => (product.variants ?? []) as ProductDetailVariant[],
[product.variants],
);
const mainImage = images[selectedImageIndex] ?? images[0]; const mainImage = images[selectedImageIndex] ?? images[0];
const dimensions = useMemo( const dimensions = useMemo(
() => getAttributeDimensions(variants), () => getAttributeDimensions(variants),

View File

@@ -23,7 +23,7 @@ export async function ProductDetailHeroSectionWrapper({
return ( return (
<ProductDetailHeroSection <ProductDetailHeroSection
product={product as any} product={product}
category={category} category={category}
subCategory={subCategory} subCategory={subCategory}
/> />

View File

@@ -1,4 +1,3 @@
import { Suspense } from "react";
import { fetchQuery } from "convex/nextjs"; import { fetchQuery } from "convex/nextjs";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { api } from "../../../../../../convex/_generated/api"; import { api } from "../../../../../../convex/_generated/api";

View File

@@ -22,16 +22,6 @@ type ShopFilterContentProps = {
isLoading?: boolean; isLoading?: boolean;
}; };
const FILTER_SECTION_IDS = [
"brand",
"tags",
"petSize",
"ageRange",
"specialDiet",
"material",
"flavor",
] as const;
export function ShopFilterContent({ export function ShopFilterContent({
options, options,
value, value,

View File

@@ -9,7 +9,6 @@ import type { ShopFilterState } from "@/lib/shop/filterState";
* otherwise renders children (e.g. sub-category links). * otherwise renders children (e.g. sub-category links).
*/ */
export function ShopFilterSidebar({ export function ShopFilterSidebar({
children,
className, className,
filterOptions, filterOptions,
filterState, filterState,

View File

@@ -10,7 +10,6 @@ export function ShopToolbar({
currentSort, currentSort,
onSortChange, onSortChange,
onOpenFilter, onOpenFilter,
resultCount,
}: { }: {
sortOptions: SortOption[]; sortOptions: SortOption[];
currentSort: string; currentSort: string;

View File

@@ -15,8 +15,7 @@ import { WishlistSignInPrompt } from "./state/WishlistSignInPrompt";
export function WishlistPageView() { export function WishlistPageView() {
const { isAuthenticated, isLoading: authLoading } = useConvexAuth(); const { isAuthenticated, isLoading: authLoading } = useConvexAuth();
const { items, isLoading, isEmpty } = useWishlist(); const { items, isLoading, isEmpty } = useWishlist();
const { removeItem, isRemoving, addToCart, isAddingToCart } = const { removeItem, isRemoving, addToCart } = useWishlistMutations();
useWishlistMutations();
const [removeTarget, setRemoveTarget] = useState<WishlistItem | null>(null); const [removeTarget, setRemoveTarget] = useState<WishlistItem | null>(null);
const [removingId, setRemovingId] = useState<string | null>(null); const [removingId, setRemovingId] = useState<string | null>(null);

View File

@@ -1,7 +1,6 @@
"use client"; "use client";
import Link from "next/link"; import Link from "next/link";
import { WISHLIST_PATH } from "@/lib/wishlist/constants";
function HeartIcon() { function HeartIcon() {
return ( return (

View File

@@ -3,6 +3,7 @@
import { useMutation } from "convex/react"; import { useMutation } from "convex/react";
import { useCallback } from "react"; import { useCallback } from "react";
import { api } from "../../../../../convex/_generated/api"; import { api } from "../../../../../convex/_generated/api";
import type { Id } from "../../../../../convex/_generated/dataModel";
import type { AddressFormData } from "./types"; import type { AddressFormData } from "./types";
/** /**
@@ -52,7 +53,7 @@ export function useAddressMutations(): {
data: Partial<AddressFormData> & { isValidated?: boolean }, data: Partial<AddressFormData> & { isValidated?: boolean },
): Promise<void> => { ): Promise<void> => {
await updateMutation({ await updateMutation({
id: id as any, id: id as Id<"addresses">,
...(data.firstName !== undefined && { firstName: data.firstName }), ...(data.firstName !== undefined && { firstName: data.firstName }),
...(data.lastName !== undefined && { lastName: data.lastName }), ...(data.lastName !== undefined && { lastName: data.lastName }),
...(data.phone !== undefined && { phone: data.phone }), ...(data.phone !== undefined && { phone: data.phone }),
@@ -69,14 +70,14 @@ export function useAddressMutations(): {
const setDefault = useCallback( const setDefault = useCallback(
async (id: string): Promise<void> => { async (id: string): Promise<void> => {
await setDefaultMutation({ id: id as any }); await setDefaultMutation({ id: id as Id<"addresses"> });
}, },
[setDefaultMutation], [setDefaultMutation],
); );
const markValidated = useCallback( const markValidated = useCallback(
async (id: string, isValidated: boolean): Promise<void> => { async (id: string, isValidated: boolean): Promise<void> => {
await markValidatedMutation({ id: id as any, isValidated }); await markValidatedMutation({ id: id as Id<"addresses">, isValidated });
}, },
[markValidatedMutation], [markValidatedMutation],
); );

View File

@@ -17,7 +17,7 @@ describe("useClickOutside", () => {
it("does not call handler when enabled is false", () => { it("does not call handler when enabled is false", () => {
const handler = vi.fn(); const handler = vi.fn();
const { result } = renderHook(() => { renderHook(() => {
const ref = useRef<HTMLDivElement>(document.createElement("div")); const ref = useRef<HTMLDivElement>(document.createElement("div"));
useClickOutside([ref], handler, false); useClickOutside([ref], handler, false);
return ref; return ref;