diff --git a/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx b/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx index bde83af..ac9102c 100644 --- a/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx +++ b/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useParams, useRouter, useSearchParams } from "next/navigation"; -import type { CategoryId } from "@/types/builderSlots"; +import type { BuilderSlotKey } from "@/types/builderSlots"; import { PART_ROLE_TO_CATEGORY, normalizePartRole } from "@/lib/catalogMappings"; /** @@ -46,7 +46,7 @@ type GunbuilderProductFromApi = { offers?: OfferFromApi[] | null; }; -type BuildState = Partial>; +type BuildState = Partial>; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8080"; diff --git a/app/admin/email/manage/page.tsx b/app/admin/email/manage/page.tsx index b312210..11a61a8 100644 --- a/app/admin/email/manage/page.tsx +++ b/app/admin/email/manage/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useAuth } from "@/context/AuthContext"; import { fetchEmailRequests, @@ -32,13 +32,13 @@ export default function AdminEmailPage() { const [statusTab, setStatusTab] = useState("ALL"); - const loadEmails = async () => { + const loadEmails = useCallback(async () => { if (!token) return; try { setLoading(true); const data = await fetchEmailRequests(token); - setEmails(data.data); + setEmails(data); setError(null); } catch (e: any) { console.error(e); @@ -46,11 +46,11 @@ export default function AdminEmailPage() { } finally { setLoading(false); } - }; + }, [token]); useEffect(() => { loadEmails(); - }, [token]); + }, [loadEmails]); const normalizeStatus = (s: unknown) => String(s ?? "").trim().toUpperCase(); diff --git a/app/admin/email/page.tsx b/app/admin/email/page.tsx index 4ba1be8..6c03355 100644 --- a/app/admin/email/page.tsx +++ b/app/admin/email/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useAuth } from "@/context/AuthContext"; import { fetchEmailRequests, @@ -27,13 +27,13 @@ export default function AdminEmailPage() { }); const [submitting, setSubmitting] = useState(false); - const loadEmails = async () => { + const loadEmails = useCallback(async () => { if (!token) return; - + try { setLoading(true); const data = await fetchEmailRequests(token); - setEmails(data.data); + setEmails(data); setError(null); } catch (e: any) { console.error(e); @@ -41,11 +41,11 @@ export default function AdminEmailPage() { } finally { setLoading(false); } - }; + }, [token]); useEffect(() => { loadEmails(); - }, [token]); + }, [loadEmails]); const handleCreate = () => { setModalMode("create"); diff --git a/app/admin/email/send/page-sendText.tsx b/app/admin/email/send/page-sendText.tsx index 2151763..a637d7d 100644 --- a/app/admin/email/send/page-sendText.tsx +++ b/app/admin/email/send/page-sendText.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { sendEmailAction, type ApiResponse, type EmailRequest } from "/app/actions/sendEmail"; +import { sendEmailAction, type ApiResponse, type EmailRequest } from "@/app/actions/sendEmail"; import { Button, Field, Input, Textarea } from "@/components/ui/form"; export default function SendEmailForm(): JSX.Element { diff --git a/app/admin/merchant-category-mappings/page.tsx b/app/admin/merchant-category-mappings/page.tsx index 567deee..6a3a90b 100644 --- a/app/admin/merchant-category-mappings/page.tsx +++ b/app/admin/merchant-category-mappings/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useApi } from "@/lib/useApi"; type Merchant = { @@ -91,7 +91,7 @@ export default function MerchantCategoryMappingPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // 2) Load mappings when merchant changes (THIS sends merchantId) - useEffect(() => { + const loadMappings = useCallback(() => { if (selectedMerchantId == null) return; setLoadingMappings(true); @@ -112,7 +112,11 @@ export default function MerchantCategoryMappingPage() { ); }) .finally(() => setLoadingMappings(false)); - }, [selectedMerchantId]); + }, [selectedMerchantId, get]); + + useEffect(() => { + loadMappings(); + }, [loadMappings]); // 3) Save a single mapping row (adjust endpoint if yours differs) const handleSaveMapping = async ( diff --git a/app/admin/products/_components/BulkBar.tsx b/app/admin/products/_components/BulkBar.tsx index 08a77dd..c749017 100644 --- a/app/admin/products/_components/BulkBar.tsx +++ b/app/admin/products/_components/BulkBar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import type { CaliberDto, PlatformDto } from "../_lib/types"; import { RightDrawer } from "./RightDrawer"; @@ -91,12 +91,10 @@ export function BulkBar(props: { const [open, setOpen] = useState(false); - if (selectedCount <= 0) return null; - const disabled = loading || selectedCount <= 0; // one button to apply staged field updates - const applyFieldChanges = () => { + const applyFieldChanges = useCallback(() => { const patch: BulkUpdateSet = { classificationReason: "Admin bulk override" }; if (bulkPlatformKey) { @@ -121,7 +119,9 @@ export function BulkBar(props: { if (keys.length === 0) return; runBulk(patch); - }; + }, [bulkPlatformKey, bulkPlatformLock, bulkRole, bulkRoleLock, bulkCaliber, bulkCaliberGroup, bulkCaliberLock, bulkForceCaliber, runBulk]); + + if (selectedCount <= 0) return null; let footer: JSX.Element; // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/app/beta/magic/page.tsx b/app/beta/magic/page.tsx index c22b966..84182db 100644 --- a/app/beta/magic/page.tsx +++ b/app/beta/magic/page.tsx @@ -45,6 +45,7 @@ export default function BetaMagicPage() { // We can simulate "login" by directly setting localStorage like AuthContext does, // but easiest is just to store it here in the same shape. setSession(data.token ?? data.accessToken, { + uuid: data.uuid ?? data.id, email: data.email, displayName: data.displayName ?? null, role: data.role ?? "USER", diff --git a/components/PricingHistoryGraph.tsx b/components/PricingHistoryGraph.tsx index c4c29d7..229d6e4 100644 --- a/components/PricingHistoryGraph.tsx +++ b/components/PricingHistoryGraph.tsx @@ -1,6 +1,9 @@ "use client"; -import type { PriceHistoryPoint } from "@/app/(app)/(builder)/parts/__DELETE[partRole]/_old_prod_details/data"; +type PriceHistoryPoint = { + date: string; + price: number; +}; interface PricingHistoryGraphProps { data: PriceHistoryPoint[]; diff --git a/components/RetailersList.tsx b/components/RetailersList.tsx index 77df26a..7fff0f0 100644 --- a/components/RetailersList.tsx +++ b/components/RetailersList.tsx @@ -1,6 +1,14 @@ "use client"; -import type { RetailerOffer } from "@/app/(app)/(builder)/parts/__DELETE[partRole]/_old_prod_details/data"; +type RetailerOffer = { + id?: string | number; + retailer: string; + price: number; + originalPrice?: number; + url: string; + affiliateUrl?: string; + inStock: boolean; +}; interface RetailersListProps { retailers: RetailerOffer[]; @@ -46,7 +54,7 @@ export function RetailersList({ retailers }: RetailersListProps) {
- {retailer.retailerName} + {retailer.retailer} {isLowestPrice && retailer.inStock && ( diff --git a/components/SendEmail-oriig.tsx b/components/SendEmail-oriig.tsx index 80793e6..6cf5b62 100644 --- a/components/SendEmail-oriig.tsx +++ b/components/SendEmail-oriig.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { sendEmailAction, type ApiResponse, type EmailRequest } from "/app/actions/sendEmail"; +import { sendEmailAction, type ApiResponse, type EmailRequest } from "@/app/actions/sendEmail"; import { Button, Field, Input, Textarea } from "@/components/ui/form"; export default function SendEmailForm(): JSX.Element { diff --git a/components/SendEmail.tsx b/components/SendEmail.tsx index 1b9e9e8..d0a6d19 100644 --- a/components/SendEmail.tsx +++ b/components/SendEmail.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { sendEmailAction, type ApiResponse, type EmailRequest } from "/app/actions/sendEmail"; +import { sendEmailAction, type ApiResponse, type EmailRequest } from "@/app/actions/sendEmail"; import { Button, Field, Input } from "@/components/ui/form"; import RichTextEditor from "@/components/ui/RichTextEditor"; diff --git a/components/ui/RichTextEditor.tsx b/components/ui/RichTextEditor.tsx index 5610aa8..49308b3 100644 --- a/components/ui/RichTextEditor.tsx +++ b/components/ui/RichTextEditor.tsx @@ -1,11 +1,16 @@ "use client"; -import QuillImageResize from "quill-image-resize-module"; -Quill.register("modules/imageResize", QuillImageResize); import dynamic from "next/dynamic"; import type React from "react"; import "react-quill/dist/quill.snow.css"; +// Import Quill modules after React Quill is loaded +if (typeof window !== 'undefined') { + const Quill = require('quill'); + const QuillImageResize = require('quill-image-resize-module').default; + Quill.register("modules/imageResize", QuillImageResize); +} + const ReactQuill = dynamic(() => import("react-quill"), { ssr: false }); type RichTextEditorProps = { diff --git a/components/ui/send_email_form.tsx b/components/ui/send_email_form.tsx index 7078f2e..1a41dc2 100644 --- a/components/ui/send_email_form.tsx +++ b/components/ui/send_email_form.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; -import { sendEmailAction, type ApiResponse, type EmailRequest } from "/app/actions/sendEmail"; +import { sendEmailAction, type ApiResponse, type EmailRequest } from "@/app/actions/sendEmail"; import { Button, Field, Input } from "@/components/ui/form"; import RichTextEditor from "@/components/ui/RichTextEditor"; diff --git a/lib/buildOverlaps.ts b/lib/buildOverlaps.ts index ba21605..2fc9f48 100644 --- a/lib/buildOverlaps.ts +++ b/lib/buildOverlaps.ts @@ -1,5 +1,7 @@ // /lib/buildOverlaps.ts -import type { CategoryId } from "@/types/builderSlots"; +import type { BuilderSlotKey } from "@/types/builderSlots"; + +type CategoryId = BuilderSlotKey; export type OverlapChipModel = { key: string; @@ -25,10 +27,10 @@ export const UPPER_OVERLAP_CATEGORIES = new Set([ export const LOWER_OVERLAP_CATEGORIES = new Set([ "lower-receiver", - "lower-parts", + "lower-parts-kit", "trigger", "grip", - "safety", + "safety-selector", "buffer", "stock", ]); diff --git a/lib/catalogMappings.ts b/lib/catalogMappings.ts index 8f2c297..55721f7 100644 --- a/lib/catalogMappings.ts +++ b/lib/catalogMappings.ts @@ -41,7 +41,7 @@ export const CATEGORY_TO_PART_ROLES: Partial> = "lower-parts-kit": ["lower-parts-kit", "lower-parts"], trigger: ["trigger", "trigger-kit"], grip: ["pistol-grip", "grip"], - safety: ["safety", "safety-selector"], + "safety-selector": ["safety", "safety-selector"], buffer: ["buffer-kit", "buffer"], stock: ["stock"], diff --git a/push-image.sh b/push-image.sh index f61d053..b2439fc 100755 --- a/push-image.sh +++ b/push-image.sh @@ -2,15 +2,15 @@ set -e REGISTRY="gitea.gofwd.group" -OWNER="sean/shadow-gunbuilder-ai-proto" -IMAGE="battlbuilder" +OWNER="forward_group/ballistic-builder-spring" +IMAGE="spring-api" TAG=$(git rev-parse --short HEAD) FULL_IMAGE="$REGISTRY/$OWNER/$IMAGE" echo "Building $FULL_IMAGE:$TAG" -docker build -f frontend/Dockerfile -t $FULL_IMAGE:$TAG . +docker build -f docker/backend/Dockerfile -t $FULL_IMAGE:$TAG . echo "Tagging latest" docker tag $FULL_IMAGE:$TAG $FULL_IMAGE:latest diff --git a/types/quill-image-resize-module.d.ts b/types/quill-image-resize-module.d.ts new file mode 100644 index 0000000..109238b --- /dev/null +++ b/types/quill-image-resize-module.d.ts @@ -0,0 +1,4 @@ +declare module 'quill-image-resize-module' { + const QuillImageResize: any; + export default QuillImageResize; +}