[{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\eslint.config.mjs","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\next.config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\postcss.config.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\c\\[categorySlug]\\page.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":335,"column":59,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":335,"endColumn":62,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13531,13534],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13531,13534],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { Metadata } from \"next\";\nimport { getSupabaseClient } from \"@/lib/supabase\";\nimport { notFound } from \"next/navigation\";\nimport StoreLayout from \"@/components/StoreLayout\";\nimport StoreContent from \"@/components/StoreContent\";\nimport { buildCatalogMetadata } from \"@/lib/catalog-metadata\";\nimport { normalizeStoriesGroups } from \"@/lib/catalog-stories\";\n\nexport const revalidate = 0;\nexport const dynamic = \"force-dynamic\";\n\ntype CatalogProduct = {\n  id: string;\n  name: string;\n  price: number;\n  image_url?: string | null;\n  stock: number;\n  unit_type?: string | null;\n  price_on_request?: boolean | null;\n  min_sale_qty_enabled?: boolean | null;\n  min_sale_qty?: number | null;\n  max_sale_qty_enabled?: boolean | null;\n  max_sale_qty?: number | null;\n  allowed_sale_qty_enabled?: boolean | null;\n  allowed_sale_quantities?: number[] | null;\n  product_price_tiers?: Array<{\n    min_qty: number;\n    max_qty: number | null;\n    unit_price: number;\n    sort_order?: number | null;\n    active?: boolean | null;\n  }>;\n  product_attributes?: Array<{ id: string }>;\n  has_variations?: boolean;\n};\n\ntype CatalogSectionProduct = {\n  section_id: string;\n  product: CatalogProduct | null;\n};\nconst LEGACY_CONFIG_PREFIX = \"[[KTCFG:\";\nconst LEGACY_CONFIG_SUFFIX = \"]]\";\n\nfunction normalizeSectionConfig(config: unknown): Record<string, unknown> | null {\n  if (config && typeof config === \"object\") return config as Record<string, unknown>;\n  if (typeof config === \"string\") {\n    try {\n      const parsed = JSON.parse(config);\n      return parsed && typeof parsed === \"object\" ? (parsed as Record<string, unknown>) : null;\n    } catch {\n      return null;\n    }\n  }\n  return null;\n}\n\nfunction extractLegacySectionMetadata(rawTitle: string) {\n  const title = String(rawTitle || \"\");\n  const start = title.indexOf(LEGACY_CONFIG_PREFIX);\n  if (start === -1) return { title, legacyConfig: {} as Record<string, unknown> };\n  const end = title.indexOf(LEGACY_CONFIG_SUFFIX, start + LEGACY_CONFIG_PREFIX.length);\n  if (end === -1) return { title, legacyConfig: {} as Record<string, unknown> };\n  const encoded = title.slice(start + LEGACY_CONFIG_PREFIX.length, end);\n  const cleanTitle = `${title.slice(0, start)}${title.slice(end + LEGACY_CONFIG_SUFFIX.length)}`.trim();\n  try {\n    const parsed = JSON.parse(decodeURIComponent(encoded));\n    if (parsed && typeof parsed === \"object\") return { title: cleanTitle, legacyConfig: parsed as Record<string, unknown> };\n  } catch {}\n  return { title: cleanTitle, legacyConfig: {} as Record<string, unknown> };\n}\n\nexport async function generateMetadata(\n  props: { params: Promise<{ slug: string; categorySlug: string }> }\n): Promise<Metadata> {\n  const params = await props.params;\n  const { slug, categorySlug } = params;\n  const supabase = getSupabaseClient();\n\n  const { data: profile } = await supabase\n    .from(\"profiles\")\n    .select(\"id,slug,updated_at,catalog_name,store_name,logo_url,catalog_logo_url,catalog_meta_title,catalog_meta_description,catalog_meta_keywords,catalog_og_title,catalog_og_description,catalog_og_image_url,catalog_favicon_url,catalog_noindex,catalog_twitter_handle\")\n    .eq(\"slug\", slug)\n    .single();\n\n  if (!profile) {\n    return { title: \"Loja\" };\n  }\n\n  const { data: category } = await supabase\n    .from(\"categories\")\n    .select(\"name\")\n    .eq(\"user_id\", profile.id)\n    .eq(\"slug\", categorySlug)\n    .eq(\"is_visible\", true)\n    .maybeSingle();\n\n  const baseTitle = profile.catalog_name || profile.store_name || \"Loja\";\n  const pageTitle = category?.name ? `${category.name} | ${baseTitle}` : baseTitle;\n  return buildCatalogMetadata({\n    profile,\n    pageTitle,\n    pageDescription: category?.name\n      ? `Explore os produtos da categoria ${category.name} em ${baseTitle}.`\n      : `Explore os produtos de ${baseTitle}.`,\n    path: `/c/${categorySlug}`,\n  });\n}\n\nexport default async function StoreCategoryPage(props: { params: Promise<{ slug: string; categorySlug: string }> }) {\n  const params = await props.params;\n  const { slug, categorySlug } = params;\n  const supabase = getSupabaseClient();\n\n  const { data: profile } = await supabase\n    .from(\"profiles\")\n    .select(\"*\")\n    .eq(\"slug\", slug)\n    .single();\n\n  if (!profile) {\n    return notFound();\n  }\n\n  const { data: selectedCategory } = await supabase\n    .from(\"categories\")\n    .select(\"id,name,slug,is_visible,parent_id\")\n    .eq(\"user_id\", profile.id)\n    .eq(\"slug\", categorySlug)\n    .eq(\"is_visible\", true)\n    .maybeSingle();\n\n  if (!selectedCategory) {\n    return notFound();\n  }\n\n  let { data: rawProducts, error: rawProductsError } = await supabase\n    .from(\"products\")\n    .select(\"*, product_attributes(id), product_price_tiers(min_qty,max_qty,unit_price,sort_order,active)\")\n    .eq(\"user_id\", profile.id)\n    .eq(\"active\", true)\n    .eq(\"show_in_catalog\", true)\n    .order(\"catalog_position\", { ascending: true, nullsFirst: false })\n    .order(\"created_at\", { ascending: false });\n\n  if (rawProductsError) {\n    const message = String((rawProductsError as { message?: unknown } | null)?.message ?? \"\").toLowerCase();\n    if (message.includes(\"product_price_tiers\")) {\n      const fallback = await supabase\n        .from(\"products\")\n        .select(\"*, product_attributes(id)\")\n        .eq(\"user_id\", profile.id)\n        .eq(\"active\", true)\n        .eq(\"show_in_catalog\", true)\n        .order(\"catalog_position\", { ascending: true, nullsFirst: false })\n        .order(\"created_at\", { ascending: false });\n      rawProducts = fallback.data;\n      rawProductsError = fallback.error;\n    }\n  }\n\n  const products = rawProducts?.map((p) => ({\n    ...p,\n    has_variations: p.product_attributes && Array.isArray(p.product_attributes) && p.product_attributes.length > 0,\n  })) || [];\n\n  const { data: categories } = await supabase\n    .from(\"categories\")\n    .select(\"id, name, slug, is_visible, parent_id\")\n    .eq(\"user_id\", profile.id)\n    .eq(\"is_visible\", true)\n    .order(\"name\", { ascending: true });\n\n  const { data: sectionsData } = await supabase\n    .from(\"catalog_sections\")\n    .select(\"*\")\n    .eq(\"user_id\", profile.id)\n    .eq(\"active\", true)\n    .order(\"position\", { ascending: true });\n\n  let { data: sectionProductsData, error: sectionProductsError } = await supabase\n    .from(\"catalog_section_products\")\n    .select(\"*, product:products(*, product_attributes(id), product_price_tiers(min_qty,max_qty,unit_price,sort_order,active))\")\n    .in(\"section_id\", sectionsData?.map((s) => s.id) || [])\n    .order(\"position\", { ascending: true });\n\n  if (sectionProductsError) {\n    const message = String((sectionProductsError as { message?: unknown } | null)?.message ?? \"\").toLowerCase();\n    if (message.includes(\"product_price_tiers\")) {\n      const fallback = await supabase\n        .from(\"catalog_section_products\")\n        .select(\"*, product:products(*, product_attributes(id))\")\n        .in(\"section_id\", sectionsData?.map((s) => s.id) || [])\n        .order(\"position\", { ascending: true });\n      sectionProductsData = fallback.data;\n      sectionProductsError = fallback.error;\n    }\n  }\n\n  const sectionProducts = (sectionProductsData || []) as CatalogSectionProduct[];\n\n  const sections = sectionsData?.map((section) => {\n    const normalizedConfig = normalizeSectionConfig((section as { config?: unknown }).config);\n    const metadata = extractLegacySectionMetadata((section as { title?: string }).title || \"\");\n    const mergedConfig = { ...(metadata.legacyConfig || {}), ...(normalizedConfig || {}) };\n    const configMode =\n      mergedConfig &&\n      \"mode\" in mergedConfig\n        ? mergedConfig.mode\n        : undefined;\n    const sectionType = configMode ?? section.section_type ?? (section as { type?: unknown }).type;\n    return {\n      ...section,\n      title: metadata.title,\n      config: mergedConfig,\n      section_type: (\n        sectionType === \"all_products\" ||\n        sectionType === \"category_products\" ||\n        sectionType === \"subcategory_products\" ||\n        sectionType === \"image_banner\" ||\n        sectionType === \"category_navigator\"\n          ? sectionType\n          : \"manual_products\"\n      ) as \"manual_products\" | \"all_products\" | \"category_products\" | \"subcategory_products\" | \"image_banner\" | \"category_navigator\",\n      products: sectionProducts\n        .filter((sp) => sp.section_id === section.id)\n        .map((sp) => {\n          const product = sp.product;\n          if (!product) return null;\n          return {\n            ...product,\n            has_variations: Array.isArray(product.product_attributes) && product.product_attributes.length > 0,\n          };\n        })\n        .filter(Boolean) || [],\n    };\n  }) || [];\n\n  const slideEnabled = false;\n  const infoStripEnabledRaw = (profile as { catalog_info_strip_enabled?: boolean | null } | null)?.catalog_info_strip_enabled;\n  const infoStripEnabled = infoStripEnabledRaw === null || infoStripEnabledRaw === undefined ? true : Boolean(infoStripEnabledRaw);\n  const infoStripItemsRaw = (profile as { catalog_info_strip_items?: unknown } | null)?.catalog_info_strip_items;\n  const infoStripItems = Array.isArray(infoStripItemsRaw)\n    ? (infoStripItemsRaw as Array<{ icon?: unknown; title?: unknown; subtitle?: unknown }>).map((x) => ({\n        icon: typeof x?.icon === \"string\" ? x.icon : \"truck\",\n        title: typeof x?.title === \"string\" ? x.title : \"\",\n        subtitle: typeof x?.subtitle === \"string\" ? x.subtitle : \"\",\n      }))\n    : [];\n\n  const banners = slideEnabled\n    ? (() => {\n        return supabase\n          .from(\"catalog_banners\")\n          .select(\"id,image_url,link_type,product_id,link_url,open_in_new_tab,position,created_at\")\n          .eq(\"user_id\", profile.id)\n          .eq(\"type\", \"slide\")\n          .eq(\"active\", true)\n          .order(\"position\", { ascending: true })\n          .order(\"created_at\", { ascending: true });\n      })()\n    : null;\n\n  const bannersResult = banners ? await banners : { data: [], error: null };\n  const resolvedBanners = bannersResult.error ? [] : (bannersResult.data || []);\n\n  const selectedAnnouncementCoupon = profile.catalog_announcement_coupon_id\n    ? await supabase\n        .from(\"catalog_coupons\")\n        .select(\"id,code,type,value,active,starts_at,ends_at\")\n        .eq(\"id\", profile.catalog_announcement_coupon_id)\n        .eq(\"user_id\", profile.id)\n        .maybeSingle()\n        .then((res) => res.data)\n    : null;\n\n  const { data: storiesGroupsData } = await supabase\n    .from(\"catalog_story_groups\")\n    .select(\"id,title,thumbnail_url,position,items:catalog_story_items(id,image_url,product_id,position,product:products(id,name,price,price_on_request,image_url))\")\n    .eq(\"user_id\", profile.id)\n    .eq(\"active\", true)\n    .order(\"position\", { ascending: true });\n\n  const primaryColor = profile.primary_color || \"#4F46E5\";\n  const storeName = profile.catalog_name || profile.store_name || \"Loja\";\n  const logoUrl = profile.catalog_logo_url || profile.logo_url;\n  const storiesGroups = normalizeStoriesGroups(storiesGroupsData);\n\n  return (\n    <StoreLayout primaryColor={primaryColor} whatsappNumber={profile.whatsapp} cartScope={profile.id || slug} storeSlug={slug}>\n      <StoreContent\n        products={products || []}\n        categories={categories || []}\n        banners={resolvedBanners}\n        sections={sections}\n        slideEnabled={slideEnabled}\n        infoStripEnabled={infoStripEnabled}\n        infoStripItems={infoStripItems}\n        slug={slug}\n        primaryColor={primaryColor}\n        storeName={storeName}\n        storeSlogan={profile.catalog_slogan}\n        logoUrl={logoUrl}\n        showStoreNameInHeader={profile.catalog_show_name_in_header}\n        showStoreSloganInHeader={profile.catalog_show_slogan_in_header}\n        storeNameColor={profile.catalog_name_color}\n        storeSloganColor={profile.catalog_slogan_color}\n        logoEffectEnabled={profile.catalog_logo_effect_enabled}\n        logoEffectType={profile.catalog_logo_effect}\n        logoEffectSpeed={profile.catalog_logo_effect_speed}\n        logoEffectIntensity={profile.catalog_logo_effect_intensity}\n        whatsapp={profile.whatsapp}\n        phone={profile.phone}\n        companyPhone={profile.company_phone}\n        companyWhatsapp={profile.company_whatsapp}\n        address={profile.address}\n        cnpj={profile.cnpj}\n        catalogShowDocument={profile.catalog_show_document}\n        footerColor={profile.catalog_footer_color}\n        socialFacebook={profile.social_facebook}\n        socialInstagram={profile.social_instagram}\n        socialTwitter={profile.social_twitter}\n        socialLinkedin={profile.social_linkedin}\n        socialYoutube={profile.social_youtube}\n        announcementEnabled={profile.catalog_announcement_enabled}\n        announcementText={profile.catalog_announcement_text}\n        announcementCountdownEnabled={profile.catalog_announcement_countdown_enabled}\n        announcementCountdownEndAt={profile.catalog_announcement_countdown_end_at}\n        announcementShowCouponBadge={profile.catalog_announcement_show_coupon_badge}\n        announcementCouponBadgeColor={profile.catalog_announcement_coupon_badge_color}\n        announcementBackgroundColor={profile.catalog_announcement_background_color}\n        announcementTextColor={profile.catalog_announcement_text_color}\n        announcementButtonLabel={profile.catalog_announcement_button_label}\n        announcementButtonUrl={profile.catalog_announcement_button_url}\n        announcementSticky={profile.catalog_announcement_sticky}\n        announcementCoupon={selectedAnnouncementCoupon as any}\n        announcementCouponId={profile.catalog_announcement_coupon_id}\n        initialSelectedCategoryId={selectedCategory.id}\n        hideSections\n        productsHeading={selectedCategory.name}\n        storiesGroups={storiesGroups}\n        catalogStoriesEnabled={profile.catalog_stories_enabled ?? true}\n      />\n    </StoreLayout>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\cadastro-cliente\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\favicon.ico\\route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\head.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\layout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\loading.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\o\\[token]\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\p\\[productId]\\page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ProductCombosWidget' is defined but never used.","line":10,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":10,"endColumn":27,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"ProductCombosWidget"},"fix":{"range":[442,509],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":343,"column":69,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":343,"endColumn":72,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13093,13096],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13093,13096],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":345,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":345,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13162,13165],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13162,13165],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":353,"column":65,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":353,"endColumn":68,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13414,13417],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13414,13417],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":406,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":406,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[15592,15595],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[15592,15595],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":423,"column":71,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":423,"endColumn":74,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[16403,16406],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[16403,16406],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'selectedAnnouncementCoupon' is assigned a value but never used.","line":462,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":462,"endColumn":36},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":473,"column":19,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":473,"endColumn":22,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[18168,18171],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[18168,18171],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":484,"column":62,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":484,"endColumn":65,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[18638,18641],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[18638,18641],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":507,"column":61,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":507,"endColumn":64,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[19146,19149],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[19146,19149],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":8,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import type { Metadata } from \"next\";\nimport { getSupabaseClient } from \"@/lib/supabase\";\nimport { notFound } from \"next/navigation\";\nimport { ArrowLeft, Flame, ShoppingBag } from \"lucide-react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport StoreLayout from \"@/components/StoreLayout\";\nimport AddToCartButton from \"@/components/AddToCartButton\";\nimport ProductMobileNavShell from \"@/components/ProductMobileNavShell\";\nimport ProductCombosWidget from \"@/components/ProductCombosWidget\";\nimport ProductPageDesktopHeaderCart from \"@/components/ProductPageDesktopHeaderCart\";\nimport { sanitizeDescriptionForRender } from \"@/lib/product-description-rich-text\";\nimport ProductShowcase from \"@/components/ProductShowcase\";\nimport CatalogAnalyticsTracker from \"@/components/CatalogAnalyticsTracker\";\nimport { buildCatalogMetadata } from \"@/lib/catalog-metadata\";\nimport { ensureColorContrast, withAlpha } from \"@/lib/color-contrast\";\nimport { normalizeStoriesGroups } from \"@/lib/catalog-stories\";\n\nexport const revalidate = 0;\nexport const dynamic = \"force-dynamic\";\n\ntype PublicProductAttributeOption =\n  | string\n  | {\n      id?: string | null;\n      label?: string | null;\n      stock?: number | null;\n      [key: string]: unknown;\n    };\n\ntype PublicProductAttribute = {\n  name?: string | null;\n  options?: PublicProductAttributeOption[] | null;\n  [key: string]: unknown;\n};\n\ntype PublicProductRow = {\n  track_inventory?: boolean | null;\n  inventory_scope?: \"product\" | \"option\" | \"variant\" | null;\n  product_attributes?: PublicProductAttribute[] | null;\n  [key: string]: unknown;\n};\n\ntype ProductOptionStockRow = {\n  option_id?: string | null;\n  attribute_name?: string | null;\n  option_label?: string | null;\n  stock?: number | null;\n};\n\ntype BasicCategory = {\n  id: string;\n  name: string;\n  slug?: string | null;\n  parent_id?: string | null;\n};\n\ntype ProductTrustItem = {\n  icon: string;\n  title: string;\n};\n\nfunction withTimeout<T>(promise: Promise<T>, timeoutMs: number, fallback: T): Promise<T> {\n  let timer: ReturnType<typeof setTimeout> | null = null;\n  const timeoutPromise = new Promise<T>((resolve) => {\n    timer = setTimeout(() => resolve(fallback), timeoutMs);\n  });\n  return Promise.race([promise, timeoutPromise]).finally(() => {\n    if (timer) clearTimeout(timer);\n  });\n}\n\ntype RelatedProduct = {\n  id: string;\n  name: string;\n  price: number;\n  image_url?: string | null;\n  price_on_request?: boolean | null;\n  use_variation_price_only?: boolean | null;\n  category_id?: string | null;\n  user_id: string;\n  stock: number;\n  track_inventory?: boolean | null;\n  unit_type?: string | null;\n  has_variations?: boolean | null;\n  min_sale_qty_enabled?: boolean | null;\n  min_sale_qty?: number | null;\n  max_sale_qty_enabled?: boolean | null;\n  max_sale_qty?: number | null;\n  allowed_sale_qty_enabled?: boolean | null;\n  allowed_sale_quantities?: number[] | null;\n  product_price_tiers?: Array<{\n    min_qty: number;\n    max_qty: number | null;\n    unit_price: number;\n    sort_order?: number | null;\n    active?: boolean | null;\n  }> | null;\n  product_attributes?: Array<{\n    name?: string | null;\n    is_required?: boolean | null;\n    options?: Array<string | { label?: string | null; price?: number | null }> | null;\n  }> | null;\n  custom_fields_config?: Array<{\n    id?: string | null;\n    title?: string | null;\n    active?: boolean | null;\n  }> | null;\n};\n\nfunction resolveRelatedPriceDisplay(product: RelatedProduct) {\n  const formatMoney = (value: number) =>\n    new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(value);\n  if (product.price_on_request) {\n    return { text: \"Sob consulta\", hasWholesalePricing: false, isPremiumMeasuredPricing: false, helperText: \"\" };\n  }\n  if (product.unit_type === \"square_meter\") {\n    return { text: \"Preço sob medida\", hasWholesalePricing: false, isPremiumMeasuredPricing: true, helperText: \"Informe as medidas para calcular\" };\n  }\n  if (product.use_variation_price_only) {\n    const attributes = Array.isArray(product.product_attributes) ? product.product_attributes : [];\n    const hasRequired = attributes.some((attribute) => Boolean(attribute?.is_required));\n    const relevantAttributes = hasRequired\n      ? attributes.filter((attribute) => Boolean(attribute?.is_required))\n      : attributes;\n    let minimumVariationPrice = 0;\n    for (const attribute of relevantAttributes) {\n      const options = Array.isArray(attribute?.options) ? attribute.options : [];\n      const optionPrices = options\n        .map((option) => (typeof option === \"string\" ? 0 : Number(option?.price) || 0))\n        .filter((value) => Number.isFinite(value) && value >= 0);\n      if (!optionPrices.length) continue;\n      minimumVariationPrice += Math.min(...optionPrices);\n    }\n    return {\n      text: `A partir de ${formatMoney(Math.max(0, minimumVariationPrice))}`,\n      hasWholesalePricing: true,\n      isPremiumMeasuredPricing: false,\n      helperText: \"\",\n    };\n  }\n  const basePrice = Number(product.price) || 0;\n  const tierPrices = (product.product_price_tiers || [])\n    .filter((tier) => tier.active !== false)\n    .map((tier) => Number(tier.unit_price))\n    .filter((value) => Number.isFinite(value) && value > 0);\n  const lowestTier = tierPrices.length > 0 ? Math.min(...tierPrices) : null;\n  const showcasePrice = lowestTier !== null ? Math.min(basePrice, lowestTier) : basePrice;\n  if (lowestTier !== null && lowestTier < basePrice) {\n    return {\n      text: `A partir de ${formatMoney(showcasePrice)}`,\n      hasWholesalePricing: true,\n      isPremiumMeasuredPricing: false,\n      helperText: \"\",\n    };\n  }\n  return {\n    text: formatMoney(showcasePrice),\n    hasWholesalePricing: tierPrices.length > 0,\n    isPremiumMeasuredPricing: false,\n    helperText: \"\",\n  };\n}\n\nfunction normalizeProductTrustItems(input: unknown): ProductTrustItem[] {\n  const defaults: ProductTrustItem[] = [\n    { icon: \"truck\", title: \"Entrega Rápida e Segura\" },\n    { icon: \"shieldCheck\", title: \"Garantia de Qualidade\" },\n  ];\n  const source = Array.isArray(input) ? input : [];\n  const items = source\n    .map((item) => {\n      const rec = item as Partial<ProductTrustItem> | null;\n      return {\n        icon: typeof rec?.icon === \"string\" ? rec.icon : \"\",\n        title: typeof rec?.title === \"string\" ? rec.title : \"\",\n      };\n    })\n    .filter((item) => item.icon || item.title)\n    .slice(0, 2);\n  while (items.length < 2) items.push(defaults[items.length]);\n  return items;\n}\n\nexport async function generateMetadata(\n  props: { params: Promise<{ slug: string; productId: string }> }\n): Promise<Metadata> {\n  const params = await props.params;\n  const { slug, productId } = params;\n  const supabase = getSupabaseClient();\n\n  const { data: profile } = await supabase\n    .from(\"profiles\")\n    .select(\"id,slug,updated_at,catalog_name,store_name,logo_url,catalog_logo_url,catalog_meta_title,catalog_meta_description,catalog_meta_keywords,catalog_og_title,catalog_og_description,catalog_og_image_url,catalog_favicon_url,catalog_noindex,catalog_twitter_handle\")\n    .eq(\"slug\", slug)\n    .single();\n\n  if (!profile) {\n    return { title: \"Produto\" };\n  }\n\n  const { data: product } = await supabase\n    .from(\"products\")\n    .select(\"name,description,image_url\")\n    .eq(\"id\", productId)\n    .eq(\"user_id\", profile.id)\n    .maybeSingle();\n\n  const baseTitle = profile.catalog_name || profile.store_name || \"Loja\";\n  const productName = String(product?.name || \"\").trim();\n  const pageTitle = productName ? `${productName} | ${baseTitle}` : baseTitle;\n  const cleanDescription = String(product?.description || \"\").replace(/<[^>]*>/g, \" \").replace(/\\s+/g, \" \").trim();\n  const pageDescription = cleanDescription || (productName ? `Veja detalhes e preços de ${productName} em ${baseTitle}.` : `Conheça os produtos de ${baseTitle}.`);\n  const metadata = buildCatalogMetadata({\n    profile: {\n      ...profile,\n      catalog_og_image_url: String(product?.image_url || \"\").trim() || profile.catalog_og_image_url,\n    },\n    pageTitle,\n    pageDescription,\n    path: `/p/${productId}`,\n  });\n\n  return metadata;\n}\n\nfunction applyOptionStocksToAttributes(\n  product: PublicProductRow,\n  optionStocks: ProductOptionStockRow[]\n): PublicProductRow {\n  const attributes = Array.isArray(product.product_attributes) ? product.product_attributes : [];\n  if (!attributes.length || !optionStocks.length) return product;\n\n  const byId = new Map<string, ProductOptionStockRow>();\n  const byAttrLabel = new Map<string, ProductOptionStockRow>();\n  for (const row of optionStocks) {\n    const optionId = typeof row.option_id === \"string\" ? row.option_id : \"\";\n    const attrName = typeof row.attribute_name === \"string\" ? row.attribute_name.trim().toLowerCase() : \"\";\n    const label = typeof row.option_label === \"string\" ? row.option_label.trim().toLowerCase() : \"\";\n    if (optionId) byId.set(optionId, row);\n    if (attrName && label) byAttrLabel.set(`${attrName}::${label}`, row);\n  }\n\n  const nextAttributes = attributes.map((attribute) => {\n    const attributeName = typeof attribute.name === \"string\" ? attribute.name.trim().toLowerCase() : \"\";\n    const options = Array.isArray(attribute.options) ? attribute.options : [];\n    const nextOptions = options.map((option) => {\n      if (!option || typeof option === \"string\") return option;\n      const optionId = typeof option.id === \"string\" ? option.id : \"\";\n      const optionLabel = typeof option.label === \"string\" ? option.label.trim().toLowerCase() : \"\";\n      const directMatch = optionId ? byId.get(optionId) : undefined;\n      const fallbackMatch = !directMatch && attributeName && optionLabel\n        ? byAttrLabel.get(`${attributeName}::${optionLabel}`)\n        : undefined;\n      const matched = directMatch || fallbackMatch;\n      if (!matched) return option;\n      const nextStock = Number.isFinite(Number(matched.stock)) ? Number(matched.stock) : option.stock;\n      return { ...option, stock: nextStock };\n    });\n    return { ...attribute, options: nextOptions };\n  });\n\n  return {\n    ...product,\n    product_attributes: nextAttributes,\n  };\n}\n\nexport default async function ProductPage(props: { params: Promise<{ slug: string; productId: string }> }) {\n  const params = await props.params;\n  const { slug, productId } = params;\n  const supabase = getSupabaseClient();\n\n  const { data: profile } = await supabase.from(\"profiles\").select(\"*\").eq(\"slug\", slug).single();\n  if (!profile) return notFound();\n\n  const baseSelect = \"*, product_attributes(*), product_variations(*)\";\n  const selectWithExtras = `${baseSelect}, product_price_tiers(min_qty,max_qty,unit_price,sort_order,active), product_images(id,image_url,sort_order,is_primary)`;\n\n  let { data: product, error: productError } = await supabase\n    .from(\"products\")\n    .select(selectWithExtras)\n    .eq(\"id\", productId)\n    .eq(\"user_id\", profile.id)\n    .order(\"position\", { ascending: true, foreignTable: \"product_attributes\" })\n    .single();\n\n  if (productError) {\n    const message = String((productError as { message?: unknown } | null)?.message ?? \"\").toLowerCase();\n    const missingTiers = message.includes(\"product_price_tiers\");\n    const missingImages = message.includes(\"product_images\");\n\n    if (missingTiers || missingImages) {\n      const fallbackFields = [\n        baseSelect,\n        missingTiers ? \"\" : \"product_price_tiers(min_qty,max_qty,unit_price,sort_order,active)\",\n        missingImages ? \"\" : \"product_images(id,image_url,sort_order,is_primary)\",\n      ]\n        .filter(Boolean)\n        .join(\", \");\n\n      const fallback = await supabase\n        .from(\"products\")\n        .select(fallbackFields)\n        .eq(\"id\", productId)\n        .eq(\"user_id\", profile.id)\n        .order(\"position\", { ascending: true, foreignTable: \"product_attributes\" })\n        .single();\n      product = fallback.data;\n      productError = fallback.error;\n    }\n  }\n\n  if (!product) return notFound();\n\n  if ((product as PublicProductRow).track_inventory !== false && (product as PublicProductRow).inventory_scope === \"option\") {\n    const { data: optionStocksData, error: optionStocksError } = await supabase\n      .from(\"product_option_stocks\")\n      .select(\"option_id,attribute_name,option_label,stock\")\n      .eq(\"product_id\", productId)\n      .eq(\"active\", true);\n    if (!optionStocksError && Array.isArray(optionStocksData) && optionStocksData.length > 0) {\n      product = applyOptionStocksToAttributes(\n        product as PublicProductRow,\n        optionStocksData as ProductOptionStockRow[]\n      ) as typeof product;\n    }\n  }\n\n  const storeName = profile.catalog_name || profile.store_name || \"Loja\";\n  const logoUrl = profile.catalog_logo_url || profile.logo_url;\n\n  const [categoryResult, categoriesResult] = await Promise.all([\n    product.category_id\n      ? withTimeout(\n          supabase\n            .from(\"categories\")\n            .select(\"id,name,slug,parent_id\")\n            .eq(\"id\", product.category_id)\n            .eq(\"user_id\", profile.id)\n            .single(),\n          3500,\n          { data: null, error: { message: \"category-timeout\" } } as any\n        )\n      : Promise.resolve({ data: null, error: null } as any),\n    withTimeout(\n      supabase\n        .from(\"categories\")\n        .select(\"id,name,slug,parent_id\")\n        .eq(\"user_id\", profile.id)\n        .eq(\"is_visible\", true),\n      3500,\n      { data: [], error: { message: \"categories-timeout\" } } as any\n    ),\n  ]);\n\n  let categoryName = \"Produto\";\n  let selectedCategory: BasicCategory | null = null;\n  const category = categoryResult?.data as BasicCategory | null;\n  if (category) {\n    categoryName = category.name;\n    selectedCategory = category;\n  }\n\n  const categories = ((categoriesResult?.data as BasicCategory[] | null) || []) as BasicCategory[];\n  const descendantsByParent = new Map<string, string[]>();\n  categories.forEach((category) => {\n    const parentId = String(category.parent_id || \"\");\n    if (!parentId) return;\n    const list = descendantsByParent.get(parentId) || [];\n    list.push(category.id);\n    descendantsByParent.set(parentId, list);\n  });\n  const categoryScope = new Set<string>();\n  if (selectedCategory?.id) {\n    const isSubcategory = Boolean(selectedCategory.parent_id);\n    if (isSubcategory) {\n      categoryScope.add(selectedCategory.id);\n    } else {\n      categoryScope.add(selectedCategory.id);\n      const stack = [selectedCategory.id];\n      while (stack.length > 0) {\n        const current = stack.pop() as string;\n        const children = descendantsByParent.get(current) || [];\n        children.forEach((childId) => {\n          if (categoryScope.has(childId)) return;\n          categoryScope.add(childId);\n          stack.push(childId);\n        });\n      }\n    }\n  }\n\n  let relatedProductsData: RelatedProduct[] = [];\n  const richRelatedQuery = await withTimeout(\n    supabase\n      .from(\"products\")\n      .select(\"id,name,price,image_url,price_on_request,use_variation_price_only,category_id,user_id,stock,track_inventory,unit_type,has_variations,min_sale_qty_enabled,min_sale_qty,max_sale_qty_enabled,max_sale_qty,allowed_sale_qty_enabled,allowed_sale_quantities,custom_fields_config,product_attributes(name,options,is_required,position,allow_multiple),product_price_tiers(min_qty,max_qty,unit_price,sort_order,active)\")\n      .eq(\"user_id\", profile.id)\n      .eq(\"active\", true)\n      .eq(\"show_in_catalog\", true)\n      .order(\"catalog_position\", { ascending: true, nullsFirst: false })\n      .order(\"created_at\", { ascending: false })\n      .limit(24),\n    4500,\n    { data: null, error: { message: \"related-timeout\" } } as any\n  );\n\n  if (!richRelatedQuery.error && Array.isArray(richRelatedQuery.data)) {\n    relatedProductsData = richRelatedQuery.data as RelatedProduct[];\n  } else {\n    const fallbackRelatedQuery = await withTimeout(\n      supabase\n        .from(\"products\")\n        .select(\"id,name,price,image_url,price_on_request,use_variation_price_only,category_id,user_id,stock,track_inventory,unit_type,custom_fields_config,product_attributes(name,options,is_required,position,allow_multiple)\")\n        .eq(\"user_id\", profile.id)\n        .eq(\"active\", true)\n        .eq(\"show_in_catalog\", true)\n        .order(\"catalog_position\", { ascending: true, nullsFirst: false })\n        .order(\"created_at\", { ascending: false })\n        .limit(24),\n      4500,\n      { data: [], error: { message: \"fallback-related-timeout\" } } as any\n    );\n    relatedProductsData = ((fallbackRelatedQuery.data || []) as RelatedProduct[]).map((item) => ({\n      ...item,\n      has_variations: false,\n      min_sale_qty_enabled: false,\n      min_sale_qty: null,\n      max_sale_qty_enabled: false,\n      max_sale_qty: null,\n      allowed_sale_qty_enabled: false,\n      allowed_sale_quantities: null,\n      product_attributes: [],\n      product_price_tiers: [],\n    }));\n  }\n\n  const relatedProducts = relatedProductsData\n    .filter((item) => item.id !== product.id)\n    .filter((item) => {\n      if (!categoryScope.size) return true;\n      return item.category_id ? categoryScope.has(item.category_id) : false;\n    })\n    .slice(0, 8);\n\n  const primaryColor = profile.primary_color || \"#4F46E5\";\n  const cardPriceColor = ensureColorContrast(primaryColor, \"#FFFFFF\", {\n    dark: \"#111827\",\n    light: \"#FFFFFF\",\n    minRatio: 2.2,\n  });\n  const backControlColor = ensureColorContrast(\"#FFFFFF\", primaryColor, {\n    dark: \"#0f172a\",\n    light: \"#FFFFFF\",\n    minRatio: 3.2,\n  });\n  const isBackControlLight = backControlColor.toLowerCase() === \"#ffffff\";\n  const productTrustItems = normalizeProductTrustItems(profile.catalog_product_trust_items);\n  const productDescriptionHtml = sanitizeDescriptionForRender(product.description);\n\n  const [selectedAnnouncementCoupon, storiesGroupsResult, combosResult] = await Promise.all([\n    profile.catalog_announcement_coupon_id\n      ? withTimeout(\n          supabase\n            .from(\"catalog_coupons\")\n            .select(\"id,code,type,value,active,starts_at,ends_at\")\n            .eq(\"id\", profile.catalog_announcement_coupon_id)\n            .eq(\"user_id\", profile.id)\n            .maybeSingle()\n            .then((res) => res.data),\n          3000,\n          null as any\n        )\n      : Promise.resolve(null),\n    withTimeout(\n      supabase\n        .from(\"catalog_story_groups\")\n        .select(\"id,title,thumbnail_url,position,items:catalog_story_items(id,image_url,product_id,position,product:products(id,name,price,price_on_request,image_url))\")\n        .eq(\"user_id\", profile.id)\n        .eq(\"active\", true)\n        .order(\"position\", { ascending: true }),\n      4000,\n      { data: [], error: { message: \"stories-timeout\" } } as any\n    ),\n    withTimeout(\n      supabase\n        .from(\"product_combos\")\n        .select(`\n      id,\n      title,\n      discount_type,\n      discount_value,\n      main_product_id,\n      combo_product:products!combo_product_id (\n        id,\n        name,\n        price,\n        image_url,\n        price_on_request,\n        use_variation_price_only\n      )\n    `)\n        .eq(\"main_product_id\", product.id)\n        .eq(\"active\", true),\n      4000,\n      { data: [], error: { message: \"combos-timeout\" } } as any\n    ),\n  ]);\n  const storiesGroups = normalizeStoriesGroups(storiesGroupsResult?.data || []);\n  const combosData = combosResult?.data || [];\n\n  return (\n    <StoreLayout primaryColor={primaryColor} whatsappNumber={profile.whatsapp} cartScope={profile.id || slug} storeSlug={slug}>\n      <CatalogAnalyticsTracker\n        slug={slug}\n        eventName=\"page_view\"\n        productId={product.id}\n        categoryId={product.category_id || null}\n        onceKey={`product_page_view:${product.id}`}\n      />\n      <CatalogAnalyticsTracker\n        slug={slug}\n        eventName=\"product_view\"\n        productId={product.id}\n        categoryId={product.category_id || null}\n        onceKey={`product_view:${product.id}`}\n      />\n      <div className=\"min-h-screen bg-gray-50/50 pb-20\">\n        <header className=\"sticky top-0 z-40 shadow-sm\" style={{ backgroundColor: primaryColor }}>\n          <div className=\"max-w-7xl mx-auto px-4 h-20 relative flex items-center justify-center\">\n            <Link\n              href={`/${slug}`}\n              className=\"absolute left-4 z-10 inline-flex items-center gap-1.5 text-sm font-medium transition-colors\"\n              style={{\n                color: backControlColor,\n                textShadow: isBackControlLight ? \"0 1px 2px rgba(0,0,0,0.35)\" : \"none\",\n              }}\n            >\n              <ArrowLeft size={15} />\n              Voltar\n            </Link>\n            {logoUrl ? (\n              <Link href={`/${slug}`} className=\"inline-flex items-center justify-center\">\n                <Image\n                  src={logoUrl}\n                  alt={storeName}\n                  className=\"h-11 w-auto object-contain\"\n                  width={96}\n                  height={44}\n                  unoptimized\n                />\n              </Link>\n            ) : null}\n            <div className=\"absolute right-4 z-10 hidden md:flex\">\n              <ProductPageDesktopHeaderCart backControlColor={backControlColor} isBackControlLight={isBackControlLight} />\n            </div>\n          </div>\n        </header>\n\n        <main className=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12\">\n          <ProductShowcase\n            product={product}\n            primaryColor={primaryColor}\n            whatsappNumber={profile.whatsapp}\n            categoryName={categoryName}\n            productDescriptionHtml={productDescriptionHtml}\n            productTrustItems={productTrustItems}\n            combosData={combosData || undefined}\n          />\n\n          {relatedProducts.length > 0 ? (\n            <section className=\"mt-10\">\n              <h2 className=\"text-xl font-bold text-gray-900 mb-4\">Produtos relacionados</h2>\n              <div className=\"grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 sm:gap-5\">\n                {relatedProducts.map((item) => {\n                  const priceDisplay = resolveRelatedPriceDisplay(item);\n                  return (\n                    <article\n                      key={item.id}\n                      className=\"group relative rounded-[22px] border border-gray-100 bg-white overflow-hidden shadow-sm hover:shadow-[0_10px_25px_rgba(15,23,42,0.08)] transition-all duration-300\"\n                    >\n                      <Link href={`/${slug}/p/${item.id}`} className=\"block relative aspect-square overflow-hidden bg-gray-50\">\n                        {item.image_url ? (\n                          <Image\n                            src={item.image_url}\n                            alt={item.name}\n                            className=\"object-cover w-full h-full transition-transform duration-700 group-hover:scale-105\"\n                            fill\n                            sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 25vw, 20vw\"\n                            unoptimized\n                          />\n                        ) : (\n                          <div className=\"w-full h-full flex items-center justify-center\">\n                            <ShoppingBag className=\"text-gray-200 w-10 h-10\" />\n                          </div>\n                        )}\n                        <div className=\"absolute inset-0 bg-gradient-to-t from-black/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300\" />\n                        {item.unit_type === \"square_meter\" && (\n                          <div className=\"absolute top-2.5 left-2.5\">\n                            <span className=\"text-[9px] font-semibold uppercase tracking-wider px-2 py-0.5 rounded-full bg-white/90 backdrop-blur-sm text-gray-800 shadow-sm border border-white/50\">\n                              m²\n                            </span>\n                          </div>\n                        )}\n                        {item.track_inventory !== false && Number(item.stock || 0) <= 0 && (\n                          <div className=\"absolute inset-0 bg-white/60 backdrop-blur-[1px] flex items-center justify-center\">\n                            <span className=\"text-gray-800 font-medium px-3 py-1 bg-white shadow-sm border border-gray-100 rounded-full text-xs\">Esgotado</span>\n                          </div>\n                        )}\n                      </Link>\n\n                      <div className=\"p-3 flex flex-col flex-1\">\n                        <div className=\"flex-1 min-h-0\">\n                          <h3 className=\"font-medium text-[13px] leading-[1.35] text-gray-600 line-clamp-2 mb-1.5 h-[2.2rem]\" title={item.name}>\n                            {item.name}\n                          </h3>\n\n                          <div className=\"h-5 mb-2 flex items-center\">\n                            {priceDisplay.hasWholesalePricing ? (\n                              <span className=\"inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-orange-50 border border-orange-100/50\">\n                                <Flame size={9} className=\"text-orange-400 fill-orange-400\" />\n                                <span className=\"text-[9px] font-bold uppercase tracking-wider text-orange-600/90\">Atacado</span>\n                              </span>\n                            ) : priceDisplay.isPremiumMeasuredPricing ? (\n                              <span className=\"inline-flex items-center rounded-full border border-violet-200/90 bg-violet-50/90 px-2 py-0.5 text-[9px] font-bold uppercase tracking-wider text-violet-700\">\n                                Sob medida\n                              </span>\n                            ) : null}\n                          </div>\n                        </div>\n\n                        <div className=\"mt-auto flex items-end justify-between gap-2\">\n                          <div className=\"flex-1 min-w-0 flex flex-col justify-end\">\n                            {priceDisplay.hasWholesalePricing && (\n                              <span className=\"text-[9px] text-gray-400 font-medium leading-none mb-0.5 tracking-wide\">A partir de</span>\n                            )}\n                            <p className=\"text-[17px] font-semibold leading-none tracking-tight\" style={{ color: cardPriceColor }}>\n                              {priceDisplay.hasWholesalePricing ? priceDisplay.text.replace(\"A partir de \", \"\") : priceDisplay.text}\n                            </p>\n                            {priceDisplay.isPremiumMeasuredPricing && (\n                              <span className=\"mt-1 text-[10px] leading-tight text-gray-500\">{priceDisplay.helperText}</span>\n                            )}\n                          </div>\n                          <div className=\"shrink-0 self-end\">\n                            <AddToCartButton\n                              product={{\n                                ...item,\n                                image_url: item.image_url || undefined,\n                                has_variations: item.has_variations === true,\n                              }}\n                              primaryColor={cardPriceColor}\n                              slug={slug}\n                              variant=\"icon\"\n                            />\n                          </div>\n                        </div>\n                      </div>\n                    </article>\n                  );\n                })}\n              </div>\n              <p className=\"text-xs mt-3\" style={{ color: withAlpha(\"#111827\", 0.62) }}>\n                {selectedCategory?.parent_id\n                  ? \"Sugestões da mesma subcategoria.\"\n                  : \"Sugestões da mesma categoria.\"}\n              </p>\n            </section>\n          ) : null}\n        </main>\n\n        <ProductMobileNavShell\n          slug={slug}\n          profileId={profile.id}\n          primaryColor={primaryColor}\n          logoUrl={logoUrl}\n          storeName={storeName}\n          storeSlogan={profile.catalog_slogan}\n          whatsappDigits={profile.company_whatsapp || profile.whatsapp || \"\"}\n          categories={categories}\n          announcementCouponId={profile.catalog_announcement_coupon_id}\n          initialSelectedCategoryId={product.category_id || undefined}\n          storiesGroups={storiesGroups}\n          catalogStoriesEnabled={profile.catalog_stories_enabled ?? true}\n        />\n      </div>\n    </StoreLayout>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\status\\[token]\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\[slug]\\template.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\api\\catalog\\events\\route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\layout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\app\\page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\AddToCartButton.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\CartSidebar.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'isMainItem' is assigned a value but never used.","line":353,"column":25,"nodeType":"Identifier","messageId":"unusedVar","endLine":353,"endColumn":35}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useCart } from \"@/context/CartContext\";\nimport { X, Minus, Plus, ShoppingBag, Trash2, Tag, Flame } from \"lucide-react\";\nimport { motion, AnimatePresence } from \"framer-motion\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { normalizeWhatsappForLink } from \"@/lib/phone\";\nimport { calculateEffectiveQuantity, checkSaleQuantityLimits } from \"@/lib/price-tiers\";\nimport { ensureColorContrast } from \"@/lib/color-contrast\";\nimport { getSupabaseClient } from \"@/lib/supabase\";\nimport { trackCatalogEvent } from \"@/lib/catalog-analytics\";\n\nconst EPSILON = 0.000001;\n\nfunction getAllowedQuantitiesFromItem(item: {\n  allowed_sale_qty_enabled?: boolean;\n  allowed_sale_quantities?: number[] | null;\n}) {\n  if (!item.allowed_sale_qty_enabled || !Array.isArray(item.allowed_sale_quantities)) return [];\n  const values = item.allowed_sale_quantities\n    .map((value) => Number(value))\n    .filter((value) => Number.isFinite(value) && value > 0)\n    .map((value) => Number(value.toFixed(3)));\n  if (!values.length) return [];\n  return Array.from(new Set(values)).sort((a, b) => a - b);\n}\n\nfunction resolveAdjacentAllowedQuantity(allowedValues: number[], currentValue: number, direction: -1 | 1) {\n  if (!allowedValues.length) return null;\n  if (direction < 0) {\n    for (let index = allowedValues.length - 1; index >= 0; index -= 1) {\n      if (allowedValues[index] + EPSILON < currentValue) return allowedValues[index];\n    }\n    return null;\n  }\n  for (let index = 0; index < allowedValues.length; index += 1) {\n    if (allowedValues[index] - EPSILON > currentValue) return allowedValues[index];\n  }\n  return null;\n}\n\nexport default function CartSidebar({ primaryColor = \"#4F46E5\", whatsappNumber, storeSlug }: { primaryColor?: string, whatsappNumber?: string, storeSlug?: string }) {\n  const { isCartOpen, toggleCart, items, updateQuantity, removeFromCart, clearCart, cartSubtotal, discountTotal, cartTotal, applyCoupon, clearCoupon, appliedCoupon } = useCart();\n  const hasOnRequest = items.some((i) => i.price_on_request);\n  const [couponInput, setCouponInput] = useState(\"\");\n  const [couponStatus, setCouponStatus] = useState<{ type: \"success\" | \"error\"; text: string } | null>(null);\n  const [applying, setApplying] = useState(false);\n  const [checkoutModalOpen, setCheckoutModalOpen] = useState(false);\n  const [checkoutStatus, setCheckoutStatus] = useState<{ type: \"success\" | \"error\"; text: string } | null>(null);\n  const [quantityStatus, setQuantityStatus] = useState<string | null>(null);\n  const [checkoutSubmitting, setCheckoutSubmitting] = useState(false);\n  const whatsappLinkDigits = normalizeWhatsappForLink(whatsappNumber);\n\n  const badgeTextColor = ensureColorContrast(primaryColor, \"#FFFFFF\", {\n    dark: \"#111827\",\n    light: \"#FFFFFF\",\n    minRatio: 2.5,\n  });\n\n  const formatMoney = (value: number) =>\n    new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(value);\n\n  const formatQuantityLimit = (value: number | null) => {\n    if (value === null || !Number.isFinite(value)) return \"0\";\n    const rounded = Number(value.toFixed(3));\n    return Number.isInteger(rounded)\n      ? String(rounded)\n      : new Intl.NumberFormat(\"pt-BR\", { minimumFractionDigits: 0, maximumFractionDigits: 3 }).format(rounded);\n  };\n\n  const getCheckForQuantity = (item: typeof items[number], quantity: number) =>\n    checkSaleQuantityLimits({\n      min_sale_qty_enabled: item.min_sale_qty_enabled,\n      min_sale_qty: item.min_sale_qty,\n      max_sale_qty_enabled: item.max_sale_qty_enabled,\n      max_sale_qty: item.max_sale_qty,\n      allowed_sale_qty_enabled: item.allowed_sale_qty_enabled,\n      allowed_sale_quantities: item.allowed_sale_quantities,\n      effectiveQuantity: calculateEffectiveQuantity({\n        quantity,\n        unit_type: item.unit_type,\n        width_m: item.width_m,\n        height_m: item.height_m,\n        length_m: item.length_m,\n      }),\n    });\n\n  const getQuantityRuleMessage = (check: ReturnType<typeof getCheckForQuantity>) => {\n    if (check.isBelowMin && check.minQty !== null) {\n      return `Quantidade minima: ${formatQuantityLimit(check.minQty)}.`;\n    }\n    if (check.isAboveMax && check.maxQty !== null) {\n      return `Quantidade maxima: ${formatQuantityLimit(check.maxQty)}.`;\n    }\n    if (check.isNotAllowedValue && check.allowedQuantities.length > 0) {\n      return `Use quantidade permitida: ${check.allowedQuantities\n        .map((value) => formatQuantityLimit(value))\n        .join(\", \")}.`;\n    }\n    return \"\";\n  };\n\n  const handleApplyCoupon = async () => {\n    if (applying) return;\n    setApplying(true);\n    const result = await applyCoupon(couponInput);\n    setCouponStatus({ type: result.ok ? \"success\" : \"error\", text: result.message });\n    if (result.ok) {\n      setCouponInput(\"\");\n    }\n    setApplying(false);\n  };\n\n  const handleRemoveCoupon = () => {\n    clearCoupon();\n    setCouponStatus({ type: \"success\", text: \"Cupom removido.\" });\n  };\n\n  const buildCheckoutMessage = () => {\n    const formatNumber = (value: number) =>\n      new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value);\n\n    const hasOnRequest = items.some((i) => i.price_on_request);\n\n    const lines = items.map((item) => {\n      if (item.unit_type === \"square_meter\") {\n        const w = item.width_m || 0;\n        const h = item.height_m || 0;\n        const areaEach = w > 0 && h > 0 ? w * h : 0;\n        const totalArea = areaEach * item.quantity;\n        const totalValue = item.price_on_request ? null : item.price * totalArea;\n        const dims = w > 0 && h > 0 ? ` (${formatNumber(w)}m x ${formatNumber(h)}m = ${formatNumber(areaEach)}m²)` : \"\";\n        const valueLabel = item.price_on_request ? \"Sob consulta\" : formatMoney(totalValue || 0);\n        const variations = item.variation_description ? `\\n   ${item.variation_description}` : \"\";\n        return `${item.quantity}x ${item.name}${dims} - ${valueLabel}${variations}`;\n      }\n\n      const totalValue = item.price_on_request ? null : item.price * item.quantity;\n      const valueLabel = item.price_on_request ? \"Sob consulta\" : formatMoney(totalValue || 0);\n      const variations = item.variation_description ? `\\n   ${item.variation_description}` : \"\";\n      return `${item.quantity}x ${item.name} - ${valueLabel}${variations}`;\n    });\n\n    const subtotalLabel = `Subtotal: ${formatMoney(cartSubtotal)}`;\n    const discountLabel = discountTotal > 0 ? `Desconto (${appliedCoupon?.code}): -${formatMoney(discountTotal)}` : \"\";\n    const totalLabel = hasOnRequest ? `*Total parcial: ${formatMoney(cartTotal)}*` : `*Total: ${formatMoney(cartTotal)}*`;\n    const note = hasOnRequest ? `\\n\\n_Itens “Sob consulta” não entram no total parcial._` : \"\";\n\n    const summaryLines = [subtotalLabel, discountLabel, totalLabel].filter(Boolean).join(\"\\n\");\n    return `*Novo Pedido*\\n\\n${lines.join('\\n')}\\n\\n${summaryLines}${note}`;\n  };\n\n  const buildCheckoutUrl = (message: string) => {\n    if (!whatsappLinkDigits) return \"\";\n    return `https://wa.me/${whatsappLinkDigits}?text=${encodeURIComponent(message)}`;\n  };\n\n  const createPendingCatalogOrder = async (clearAfterOpen: boolean, whatsappUrl: string, message: string) => {\n    if (!storeSlug?.trim()) {\n      return { ok: true as const };\n    }\n    const supabase = getSupabaseClient();\n    const payload = {\n      currency: \"BRL\",\n      subtotal: Number(cartSubtotal.toFixed(2)),\n      discount: Number(discountTotal.toFixed(2)),\n      total: Number(cartTotal.toFixed(2)),\n      coupon_code: appliedCoupon?.code || null,\n      customer_name: null,\n      customer_phone: null,\n      customer_whatsapp: null,\n      notes: \"\",\n      whatsapp_url: whatsappUrl,\n      items: items.map((item) => ({\n        product_id: item.product_id,\n        name: item.name,\n        description: item.variation_description || \"\",\n        quantity: Number(item.quantity || 0),\n        unit_type: item.unit_type || \"unit\",\n        width_m: item.width_m ?? 0,\n        height_m: item.height_m ?? 0,\n        length_m: item.length_m ?? 0,\n        unit_price: Number(item.price || 0),\n        total_price: Number((item.price_on_request ? 0 : item.price * item.quantity).toFixed(2)),\n        variation_description: item.variation_description || \"\",\n        selected_options: item.selected_options || null,\n        raw_price_on_request: Boolean(item.price_on_request),\n      })),\n      cart_payload: {\n        clear_after_open: clearAfterOpen,\n        message,\n        has_on_request: hasOnRequest,\n      },\n    };\n\n    const { data, error } = await supabase.rpc(\"create_public_catalog_order\", {\n      p_slug: storeSlug,\n      p_payload: payload,\n    });\n\n    if (error) {\n      return { ok: false as const, message: error.message || \"Não foi possível registrar o pedido.\" };\n    }\n    if (!data || typeof data !== \"object\") {\n      return { ok: false as const, message: \"Não foi possível registrar o pedido.\" };\n    }\n    return { ok: true as const };\n  };\n\n  const handleCheckout = async (clearAfterOpen: boolean) => {\n    if (checkoutSubmitting) return;\n    const message = buildCheckoutMessage();\n    const url = buildCheckoutUrl(message);\n    if (!url) return;\n    if (storeSlug?.trim()) {\n      void trackCatalogEvent({\n        slug: storeSlug,\n        eventName: \"checkout_started\",\n        meta: {\n          items_count: items.length,\n          cart_total: Number(cartTotal.toFixed(2)),\n          clear_after_open: clearAfterOpen,\n        },\n      });\n    }\n    setCheckoutSubmitting(true);\n\n    const pendingResult = await createPendingCatalogOrder(clearAfterOpen, url, message);\n    if (!pendingResult.ok) {\n      setCheckoutSubmitting(false);\n      setCheckoutStatus({\n        type: \"error\",\n        text: pendingResult.message,\n      });\n      return;\n    }\n    if (storeSlug?.trim()) {\n      void trackCatalogEvent({\n        slug: storeSlug,\n        eventName: \"order_created\",\n        meta: {\n          items_count: items.length,\n          cart_total: Number(cartTotal.toFixed(2)),\n          clear_after_open: clearAfterOpen,\n        },\n      });\n    }\n\n    // Do not use noopener here because some browsers return null and block our success flow.\n    const openedWindow = window.open(url, \"_blank\");\n    if (!openedWindow) {\n      setCheckoutSubmitting(false);\n      setCheckoutStatus({\n        type: \"error\",\n        text: \"Nao foi possivel abrir o WhatsApp. Verifique se o navegador bloqueou pop-ups.\",\n      });\n      return;\n    }\n\n    if (clearAfterOpen) {\n      clearCoupon();\n      clearCart();\n      setCheckoutModalOpen(false);\n      setCheckoutStatus(null);\n      if (isCartOpen) {\n        toggleCart();\n      }\n      setCheckoutSubmitting(false);\n      return;\n    }\n\n    setCheckoutModalOpen(false);\n    setCheckoutStatus({\n      type: \"success\",\n      text: \"Pedido registrado e enviado com carrinho mantido.\",\n    });\n    setCheckoutSubmitting(false);\n  };\n\n  return (\n    <AnimatePresence>\n      {isCartOpen && (\n        <>\n          <motion.div\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 0.5 }}\n            exit={{ opacity: 0 }}\n            onClick={toggleCart}\n            className=\"fixed inset-0 bg-black z-[60]\"\n          />\n          <motion.div\n            initial={{ x: \"100%\" }}\n            animate={{ x: 0 }}\n            exit={{ x: \"100%\" }}\n            transition={{ type: \"tween\", duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}\n            className=\"bg-white shadow-xl z-[70] flex flex-col relative will-change-transform\"\n            style={{\n              position: \"fixed\",\n              right: 0,\n              top: 0,\n              bottom: 0,\n              left: \"auto\",\n              width: \"100%\",\n              maxWidth: \"28rem\",\n              height: \"100dvh\",\n            }}\n          >\n            <div className=\"p-4 border-b flex items-center justify-between bg-gray-50\">\n              <h2 className=\"text-lg font-bold text-gray-900 flex items-center gap-2\">\n                <ShoppingBag size={20} />\n                Seu Carrinho\n              </h2>\n              <button onClick={toggleCart} className=\"p-2 hover:bg-gray-200 rounded-full transition-colors\">\n                <X size={20} />\n              </button>\n            </div>\n\n            <div className=\"flex-1 overflow-y-auto p-4 space-y-4\">\n              {items.length === 0 ? (\n                <div className=\"h-full flex flex-col items-center justify-center text-gray-400 space-y-4\">\n                  <ShoppingBag size={48} className=\"opacity-20\" />\n                  <p>Seu carrinho está vazio</p>\n                  <button \n                    onClick={toggleCart}\n                    className=\"text-sm font-medium hover:underline\"\n                    style={{ color: primaryColor }}\n                  >\n                    Continuar comprando\n                  </button>\n                </div>\n              ) : (\n                items\n                  .filter(item => !item.combo_parent_id)\n                  .map((item) => {\n                  const quantityCheck = getCheckForQuantity(item, item.quantity);\n                  const quantityRuleMessage = getQuantityRuleMessage(quantityCheck);\n                  const allowedQuantities = getAllowedQuantitiesFromItem(item);\n                  const nextQuantityDown = allowedQuantities.length\n                    ? resolveAdjacentAllowedQuantity(allowedQuantities, item.quantity, -1)\n                    : Math.max(1, item.quantity - 1);\n                  const nextQuantityUp = allowedQuantities.length\n                    ? resolveAdjacentAllowedQuantity(allowedQuantities, item.quantity, 1)\n                    : item.quantity + 1;\n                  const canDecrease =\n                    nextQuantityDown !== null &&\n                    nextQuantityDown > 0 &&\n                    getCheckForQuantity(item, nextQuantityDown).isValid;\n                  const canIncrease =\n                    nextQuantityUp !== null &&\n                    getCheckForQuantity(item, nextQuantityUp).isValid;\n\n                  const isMainItem = !item.is_combo_item && !item.combo_parent_id;\n                    const comboChildren = items.filter(child => child.combo_parent_id === item.id);\n\n                    return (\n                      <div key={item.id} className={`flex flex-col ${item.is_combo_item && !item.combo_parent_id ? \"opacity-90\" : \"\"}`}>\n                        {/* Main Item */}\n                        <div className={`flex gap-4 bg-white p-3 rounded-xl border shadow-sm relative z-10 ${comboChildren.length > 0 ? 'border-gray-200 border-b-gray-100 rounded-b-none' : 'border-gray-100'}`}>\n                          {item.image_url ? (\n                          <Image\n                            src={item.image_url}\n                            alt={item.name}\n                            className=\"w-20 h-20 object-cover rounded-lg bg-gray-100\"\n                            width={80}\n                            height={80}\n                          />\n                      ) : (\n                          <div className=\"w-20 h-20 bg-gray-100 rounded-lg flex items-center justify-center\">\n                              <ShoppingBag className=\"text-gray-300\" />\n                          </div>\n                      )}\n                      <div className=\"flex-1 flex flex-col justify-between\">\n                        <div>\n                          <div className=\"flex items-start justify-between gap-2\">\n                            <h3 className=\"font-medium text-gray-900 line-clamp-1\">{item.name}</h3>\n                            {item.is_combo_item && (\n                              <span className=\"text-[9px] font-bold bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded uppercase tracking-wider shrink-0\">\n                                Combo\n                              </span>\n                            )}\n                          </div>\n                          {item.variation_description && (\n                            <p className=\"text-[11px] text-gray-500 line-clamp-2 mt-0.5\">\n                              {item.variation_description}\n                            </p>\n                          )}\n                          {item.unit_type === \"square_meter\" ? (\n                            <div className=\"space-y-0.5 mt-1\">\n                               <div className=\"flex items-center justify-between gap-2\">\n                                  <p className=\"text-[10px] text-gray-500 font-medium bg-gray-50 px-1.5 py-0.5 rounded\">\n                                    {item.price_on_request\n                                      ? \"Sob consulta\"\n                                      : `${formatMoney(item.price)}/m²`}\n                                  </p>\n                                  {!item.price_on_request && (\n                                     <p className=\"text-sm font-bold text-gray-900\">\n                                       {formatMoney(item.price * (item.width_m || 0) * (item.height_m || 0) * item.quantity)}\n                                     </p>\n                                  )}\n                               </div>\n                              {(item.width_m || 0) > 0 && (item.height_m || 0) > 0 && (\n                                <p className=\"text-[10px] text-gray-400\">\n                                  {new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(item.width_m as number)}m ×{\" \"}\n                                  {new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(item.height_m as number)}m\n                                </p>\n                              )}\n                            </div>\n                          ) : (\n                            <div className=\"flex items-center justify-between mt-1\">\n                               <p className=\"text-[10px] text-gray-500 font-medium bg-gray-50 px-1.5 py-0.5 rounded\">\n                                 {item.price_on_request\n                                   ? \"Sob consulta\"\n                                   : `${formatMoney(item.price)} un.`}\n                               </p>\n                               {!item.price_on_request && (\n                                 <p className=\"text-sm font-bold text-gray-900\">\n                                   {formatMoney(item.price * item.quantity)}\n                                 </p>\n                               )}\n                            </div>\n                          )}\n                        </div>\n                        <div className=\"flex items-center justify-between mt-2\">\n                          {!item.is_combo_item ? (\n                            <div className=\"flex items-center gap-2 bg-gray-50 rounded-lg p-1\">\n                              <button\n                                onClick={() => {\n                                  if (nextQuantityDown === null) return;\n                                  const result = updateQuantity(item.id, nextQuantityDown);\n                                  if (!result.ok) {\n                                    setQuantityStatus(result.message || \"Nao foi possivel atualizar a quantidade.\");\n                                    return;\n                                  }\n                                  setQuantityStatus(null);\n                                }}\n                                disabled={!canDecrease}\n                                className=\"p-1 hover:bg-white rounded-md shadow-sm transition-all disabled:opacity-40 disabled:cursor-not-allowed\"\n                              >\n                                <Minus size={14} />\n                              </button>\n                              <span className=\"text-sm font-medium w-6 text-center\">{item.quantity}</span>\n                              <button\n                                onClick={() => {\n                                  if (nextQuantityUp === null) return;\n                                  const result = updateQuantity(item.id, nextQuantityUp);\n                                  if (!result.ok) {\n                                    setQuantityStatus(result.message || \"Nao foi possivel atualizar a quantidade.\");\n                                    return;\n                                  }\n                                  setQuantityStatus(null);\n                                }}\n                                disabled={!canIncrease}\n                                className=\"p-1 hover:bg-white rounded-md shadow-sm transition-all disabled:opacity-40 disabled:cursor-not-allowed\"\n                              >\n                                <Plus size={14} />\n                              </button>\n                            </div>\n                          ) : (\n                            <div className=\"text-[11px] text-gray-500 flex items-center\">\n                              Qtd: <span className=\"font-medium text-gray-900 ml-1\">{item.quantity}</span>\n                            </div>\n                          )}\n                          <button\n                            onClick={() => removeFromCart(item.id)}\n                            className=\"text-red-400 hover:text-red-600 p-2\"\n                          >\n                            <Trash2 size={18} />\n                          </button>\n                        </div>\n                        {quantityRuleMessage && (\n                          <p className=\"mt-1 text-[11px] font-medium text-amber-700\">{quantityRuleMessage}</p>\n                        )}\n                      </div>\n                    </div>\n                    {/* Combo Children */}\n                    {comboChildren.length > 0 && (\n                      <div \n                        className=\"border border-t-0 rounded-b-xl p-3 flex flex-col gap-3 mt-[-4px] pt-4 relative z-0\"\n                        style={{ \n                          borderColor: `${primaryColor}40`,\n                          background: `linear-gradient(to bottom, ${primaryColor}25, ${primaryColor}05)`\n                        }}\n                      >\n                        {comboChildren.map(child => (\n                          <div \n                            key={child.id} \n                            className=\"relative bg-white/95 backdrop-blur-sm p-3 rounded-xl border flex flex-col gap-2 transition-all shadow-sm\"\n                            style={{ borderColor: `${primaryColor}30` }}\n                          >\n                            {/* Combo Badge */}\n                            <div \n                              className=\"absolute -top-2.5 -right-2 flex items-center gap-1 px-2 py-0.5 rounded-full text-[9px] font-bold uppercase tracking-wider shadow-sm border-2 border-white\"\n                              style={{ backgroundColor: primaryColor, color: badgeTextColor }}\n                            >\n                              <Flame size={10} strokeWidth={3} style={{ color: badgeTextColor === '#FFFFFF' ? '#FDE047' : '#D97706' }} />\n                              Compre Junto\n                            </div>\n\n                            <div className=\"flex gap-3\">\n                              {child.image_url ? (\n                                <Image src={child.image_url} alt={child.name} className=\"w-14 h-14 object-cover rounded-lg bg-gray-50 border border-gray-100 shrink-0\" width={56} height={56} />\n                              ) : (\n                                <div className=\"w-14 h-14 bg-gray-50 rounded-lg flex items-center justify-center border border-gray-100 shrink-0\"><ShoppingBag size={18} className=\"text-gray-300\" /></div>\n                              )}\n                              \n                              <div className=\"flex-1 min-w-0 flex flex-col justify-between\">\n                                <div className=\"flex items-start justify-between gap-1\">\n                                  <p className=\"text-sm font-semibold text-gray-800 line-clamp-1 pr-4\">{child.name}</p>\n                                  <button\n                                    onClick={() => removeFromCart(child.id)}\n                                    className=\"text-gray-300 hover:text-red-500 transition-colors absolute right-2 top-2\"\n                                    title=\"Remover item do combo\"\n                                  >\n                                    <X size={16} />\n                                  </button>\n                                </div>\n                                \n                                {child.variation_description && (\n                                  <p className=\"text-[11px] text-gray-500 line-clamp-1 mt-0.5\">\n                                    {child.variation_description}\n                                  </p>\n                                )}\n                                {(child.width_m || 0) > 0 && (child.height_m || 0) > 0 && (\n                                  <p className=\"text-[11px] text-gray-500 mt-0.5\">\n                                    Medidas: {new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(child.width_m as number)}m ×{\" \"}\n                                    {new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(child.height_m as number)}m\n                                  </p>\n                                )}\n\n                                <div className=\"flex items-end justify-between mt-2\">\n                                  <div className=\"flex items-center gap-1 bg-gray-50 rounded-lg p-0.5 border border-gray-200\">\n                                    <button\n                                        onClick={() => {\n                                          if (child.quantity > 1) {\n                                            updateQuantity(child.id, child.quantity - 1);\n                                          }\n                                        }}\n                                        disabled={child.quantity <= 1}\n                                        className=\"w-6 h-6 flex items-center justify-center text-gray-500 hover:text-gray-900 hover:bg-white rounded-md shadow-sm transition-all disabled:opacity-40 disabled:shadow-none\"\n                                      >\n                                        <Minus size={12} strokeWidth={2.5} />\n                                      </button>\n                                      <input \n                                        type=\"number\"\n                                        value={child.quantity}\n                                        onChange={(e) => {\n                                          let val = parseInt(e.target.value, 10);\n                                          if (isNaN(val) || val < 1) val = 1;\n                                          updateQuantity(child.id, val);\n                                        }}\n                                        className=\"w-7 text-[12px] font-bold text-center text-gray-800 focus:outline-none bg-transparent border-none p-0 hide-arrows\"\n                                        min=\"1\"\n                                      />\n                                      <button\n                                      onClick={() => {\n                                        updateQuantity(child.id, child.quantity + 1);\n                                      }}\n                                      className=\"w-6 h-6 flex items-center justify-center text-gray-500 hover:text-gray-900 hover:bg-white rounded-md shadow-sm transition-all\"\n                                    >\n                                      <Plus size={12} strokeWidth={2.5} />\n                                    </button>\n                                  </div>\n                                  <div className=\"flex flex-col items-end\">\n                                    <span className=\"text-sm font-black\" style={{ color: primaryColor }}>{formatMoney(child.price * child.quantity)}</span>\n                                  </div>\n                                </div>\n                              </div>\n                            </div>\n                          </div>\n                        ))}\n                      </div>\n                    )}\n                  </div>\n                  );\n                })\n              )}\n            </div>\n\n            {items.length > 0 && (\n              <div className=\"p-4 border-t bg-gray-50 space-y-4\">\n                <div className=\"space-y-3\">\n                  <div className=\"flex items-center justify-between text-sm text-gray-600\">\n                    <span>Subtotal</span>\n                    <span>{formatMoney(cartSubtotal)}</span>\n                  </div>\n                  {discountTotal > 0 && appliedCoupon && (\n                    <div className=\"flex items-center justify-between text-sm text-emerald-600\">\n                      <span>Desconto ({appliedCoupon.code})</span>\n                      <span>-{formatMoney(discountTotal)}</span>\n                    </div>\n                  )}\n                  <div className=\"flex items-center justify-between text-lg font-bold text-gray-900\">\n                    <span>{hasOnRequest ? \"Total parcial\" : \"Total\"}</span>\n                    <span>{formatMoney(cartTotal)}</span>\n                  </div>\n                </div>\n                <div className=\"space-y-2\">\n                  <span className=\"text-xs font-semibold text-gray-500 uppercase tracking-wide flex items-center gap-2\">\n                    <Tag size={12} />\n                    Cupom de desconto\n                  </span>\n                  <div className=\"flex items-center gap-2\">\n                    <input\n                      value={couponInput}\n                      onChange={(e) => setCouponInput(e.target.value)}\n                      placeholder=\"Digite seu cupom\"\n                      className=\"flex-1 rounded-lg border border-gray-200 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/20\"\n                    />\n                    <button\n                      onClick={handleApplyCoupon}\n                      disabled={applying || !couponInput.trim()}\n                      className=\"px-3 py-2 rounded-lg text-sm font-semibold bg-white border border-gray-200 text-gray-700 hover:bg-gray-100 disabled:opacity-50 disabled:cursor-not-allowed\"\n                    >\n                      {applying ? \"Aplicando\" : \"Aplicar\"}\n                    </button>\n                  </div>\n                  {appliedCoupon && (\n                    <div className=\"flex items-center justify-between text-xs text-emerald-600 bg-emerald-50 border border-emerald-100 rounded-lg px-3 py-2\">\n                      <span>Cupom ativo: {appliedCoupon.code}</span>\n                      <button onClick={handleRemoveCoupon} className=\"text-emerald-700 hover:underline\">\n                        Remover\n                      </button>\n                    </div>\n                  )}\n                  {couponStatus && !appliedCoupon && (\n                    <div className={`text-xs rounded-lg px-3 py-2 ${couponStatus.type === \"success\" ? \"bg-emerald-50 text-emerald-600 border border-emerald-100\" : \"bg-rose-50 text-rose-600 border border-rose-100\"}`}>\n                      {couponStatus.text}\n                    </div>\n                  )}\n                </div>\n                {hasOnRequest && (\n                  <div className=\"text-xs text-gray-500\">\n                    Itens “Sob consulta” não entram no total parcial.\n                  </div>\n                )}\n                {checkoutStatus && (\n                  <div className={`text-xs rounded-lg px-3 py-2 ${checkoutStatus.type === \"success\" ? \"bg-emerald-50 text-emerald-600 border border-emerald-100\" : \"bg-rose-50 text-rose-600 border border-rose-100\"}`}>\n                    {checkoutStatus.text}\n                  </div>\n                )}\n                {quantityStatus && (\n                  <div className=\"text-xs rounded-lg px-3 py-2 bg-amber-50 text-amber-700 border border-amber-100\">\n                    {quantityStatus}\n                  </div>\n                )}\n                <button\n                  onClick={() => {\n                    setCheckoutStatus(null);\n                    setCheckoutModalOpen(true);\n                  }}\n                  disabled={!whatsappLinkDigits || checkoutSubmitting}\n                  className=\"w-full py-3.5 px-4 rounded-xl text-white font-bold shadow-lg hover:opacity-90 active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2\"\n                  style={{ backgroundColor: whatsappLinkDigits ? '#25D366' : 'gray' }}\n                >\n                  {checkoutSubmitting ? \"Registrando pedido...\" : whatsappLinkDigits ? 'Finalizar Pedido no WhatsApp' : 'Loja sem WhatsApp configurado'}\n                </button>\n              </div>\n            )}\n            <AnimatePresence>\n              {checkoutModalOpen && items.length > 0 && (\n                <>\n                  <motion.button\n                    type=\"button\"\n                    initial={{ opacity: 0 }}\n                    animate={{ opacity: 1 }}\n                    exit={{ opacity: 0 }}\n                    onClick={() => setCheckoutModalOpen(false)}\n                    className=\"absolute inset-0 bg-black/30 z-10\"\n                  />\n                  <motion.div\n                    initial={{ opacity: 0, y: 12, scale: 0.98 }}\n                    animate={{ opacity: 1, y: 0, scale: 1 }}\n                    exit={{ opacity: 0, y: 12, scale: 0.98 }}\n                    transition={{ duration: 0.15 }}\n                    className=\"absolute left-4 right-4 bottom-4 z-20 bg-white border border-gray-200 rounded-2xl shadow-xl p-4 space-y-3\"\n                  >\n                    <h3 className=\"text-sm font-bold text-gray-900\">Como deseja finalizar?</h3>\n                    <p className=\"text-xs text-gray-600\">Abriremos o WhatsApp com este pedido.</p>\n                    <button\n                      onClick={() => handleCheckout(true)}\n                      disabled={checkoutSubmitting}\n                      className=\"w-full py-2.5 px-3 rounded-xl text-white font-semibold disabled:opacity-60\"\n                      style={{ backgroundColor: \"#25D366\" }}\n                    >\n                      {checkoutSubmitting ? \"Registrando...\" : \"Finalizar e limpar carrinho\"}\n                    </button>\n                    <button\n                      onClick={() => handleCheckout(false)}\n                      disabled={checkoutSubmitting}\n                      className=\"w-full py-2.5 px-3 rounded-xl font-semibold text-gray-700 border border-gray-200 hover:bg-gray-50 disabled:opacity-60\"\n                    >\n                      {checkoutSubmitting ? \"Registrando...\" : \"Finalizar e manter carrinho\"}\n                    </button>\n                    <button\n                      onClick={() => !checkoutSubmitting && setCheckoutModalOpen(false)}\n                      disabled={checkoutSubmitting}\n                      className=\"w-full py-2.5 px-3 rounded-xl font-semibold text-gray-500 hover:bg-gray-50 disabled:opacity-60\"\n                    >\n                      Cancelar\n                    </button>\n                  </motion.div>\n                </>\n              )}\n            </AnimatePresence>\n          </motion.div>\n        </>\n      )}\n    </AnimatePresence>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\CartTrigger.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\CatalogAnalyticsTracker.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\CatalogMobileBottomNav.tsx","messages":[{"ruleId":"react-hooks/set-state-in-effect","severity":2,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\nC:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\CatalogMobileBottomNav.tsx:37:5\n  35 |\n  36 |   useEffect(() => {\n> 37 |     setMounted(true);\n     |     ^^^^^^^^^^ Avoid calling setState() directly within an effect\n  38 |   }, []);\n  39 |\n  40 |   const showMobileCartBadge = mounted && Number(cartCount) > 0 && items.length > 0;","line":37,"column":5,"nodeType":null,"endLine":37,"endColumn":15}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport Link from \"next/link\";\nimport { Home, LayoutGrid, MessageSquare, Search, ShoppingBag } from \"lucide-react\";\nimport { getReadableTextColor } from \"@/lib/color-contrast\";\nimport { useCart } from \"@/context/CartContext\";\nimport { useEffect, useState } from \"react\";\n\ntype CatalogMobileBottomNavProps = {\n  slug: string;\n  primaryColor: string;\n  categoriesHref: string;\n  whatsappDigits?: string;\n  isSearchActive?: boolean;\n  isCategoriesActive?: boolean;\n  onHomeClick?: () => void;\n  onSearchClick?: () => void;\n  onCategoriesClick?: () => void;\n};\n\nexport default function CatalogMobileBottomNav({\n  slug,\n  primaryColor,\n  categoriesHref,\n  whatsappDigits,\n  isSearchActive = false,\n  isCategoriesActive = false,\n  onHomeClick,\n  onSearchClick,\n  onCategoriesClick,\n}: CatalogMobileBottomNavProps) {\n  const { toggleCart, cartCount, items } = useCart();\n  const [mounted, setMounted] = useState(false);\n  const primaryTextColor = getReadableTextColor(primaryColor);\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const showMobileCartBadge = mounted && Number(cartCount) > 0 && items.length > 0;\n\n  return (\n    <div className=\"md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-[60] pb-safe\">\n      <div className=\"flex items-center justify-around h-16 px-2 relative\">\n        <Link\n          href={`/${slug}`}\n          onClick={onHomeClick}\n          className=\"flex flex-col items-center justify-center w-14 gap-1 text-gray-500 hover:text-gray-900 group\"\n        >\n          <Home strokeWidth={2.5} size={22} className=\"group-hover:scale-110 transition-transform\" />\n          <span className=\"text-[10px] font-medium\">Início</span>\n        </Link>\n\n        {onSearchClick ? (\n          <button\n            onClick={onSearchClick}\n            className={`flex flex-col items-center justify-center w-14 gap-1 group ${isSearchActive ? \"text-gray-900 font-bold\" : \"text-gray-500 hover:text-gray-900\"}`}\n          >\n            <Search strokeWidth={2.5} size={22} className=\"group-hover:scale-110 transition-transform\" />\n            <span className=\"text-[10px] font-medium\">Busca</span>\n          </button>\n        ) : (\n          <Link href={`/${slug}`} className=\"flex flex-col items-center justify-center w-14 gap-1 text-gray-500 hover:text-gray-900 group\">\n            <Search strokeWidth={2.5} size={22} className=\"group-hover:scale-110 transition-transform\" />\n            <span className=\"text-[10px] font-medium\">Busca</span>\n          </Link>\n        )}\n\n        <div className=\"relative -top-5\">\n          <button\n            onClick={toggleCart}\n            className=\"flex items-center justify-center w-14 h-14 rounded-full shadow-xl border-4 border-gray-50 transition-transform active:scale-95 relative\"\n            style={{ backgroundColor: primaryColor, color: primaryTextColor }}\n          >\n            <ShoppingBag size={24} strokeWidth={2.5} />\n            {showMobileCartBadge && (\n              <span className=\"absolute -top-1 -right-1 bg-red-500 text-white text-[10px] font-bold w-5 h-5 flex items-center justify-center rounded-full border-2 border-white\">\n                {cartCount}\n              </span>\n            )}\n          </button>\n          <span className=\"absolute -bottom-5 left-1/2 -translate-x-1/2 text-[10px] font-bold text-gray-500 w-full text-center\">Carrinho</span>\n        </div>\n\n        {onCategoriesClick ? (\n          <button\n            onClick={onCategoriesClick}\n            className={`flex flex-col items-center justify-center w-14 gap-1 group ${isCategoriesActive ? \"text-gray-900 font-bold\" : \"text-gray-500 hover:text-gray-900\"}`}\n          >\n            <LayoutGrid strokeWidth={2.5} size={22} className=\"group-hover:scale-110 transition-transform\" />\n            <span className=\"text-[10px] font-medium\">Categorias</span>\n          </button>\n        ) : (\n          <Link href={categoriesHref} className=\"flex flex-col items-center justify-center w-14 gap-1 text-gray-500 hover:text-gray-900 group\">\n            <LayoutGrid strokeWidth={2.5} size={22} className=\"group-hover:scale-110 transition-transform\" />\n            <span className=\"text-[10px] font-medium\">Categorias</span>\n          </Link>\n        )}\n\n        {whatsappDigits ? (\n          <a\n            href={`https://wa.me/${whatsappDigits}`}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"flex flex-col items-center justify-center w-14 gap-1 text-gray-500 hover:text-green-600 group\"\n          >\n            <MessageSquare strokeWidth={2.5} size={22} className=\"group-hover:scale-110 transition-transform\" />\n            <span className=\"text-[10px] font-medium\">Contato</span>\n          </a>\n        ) : (\n          <div className=\"w-14\" />\n        )}\n      </div>\n    </div>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ComboConfigModal.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":17,"column":10,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":17,"endColumn":13,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[474,477],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[474,477],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":18,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":18,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[494,497],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[494,497],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":20,"column":48,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":20,"endColumn":51,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[570,573],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[570,573],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"react-hooks/set-state-in-effect","severity":2,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\nC:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ComboConfigModal.tsx:40:7\n  38 |   useEffect(() => {\n  39 |     if (isOpen) {\n> 40 |       setQuantity(1);\n     |       ^^^^^^^^^^^ Avoid calling setState() directly within an effect\n  41 |       setWidthStr(\"\");\n  42 |       setHeightStr(\"\");\n  43 |       setSelectedOptions({});","line":40,"column":7,"nodeType":null,"endLine":40,"endColumn":18},{"ruleId":"react-hooks/set-state-in-effect","severity":2,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\nC:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ComboConfigModal.tsx:57:7\n  55 |         initialQty = productData.min_sale_qty;\n  56 |       }\n> 57 |       setQuantity(initialQty);\n     |       ^^^^^^^^^^^ Avoid calling setState() directly within an effect\n  58 |     }\n  59 |   }, [isOpen, productData]);\n  60 |","line":57,"column":7,"nodeType":null,"endLine":57,"endColumn":18},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":103,"column":53,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":103,"endColumn":56,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[3914,3917],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[3914,3917],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":106,"column":45,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":106,"endColumn":48,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[4086,4089],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[4086,4089],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":158,"column":79,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":158,"endColumn":82,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5953,5956],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5953,5956],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":182,"column":26,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":182,"endColumn":29,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6827,6830],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6827,6830],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":203,"column":33,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":203,"endColumn":36,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7542,7545],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7542,7545],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":204,"column":41,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":204,"endColumn":44,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7607,7610],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7607,7610],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":209,"column":31,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":209,"endColumn":34,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7862,7865],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7862,7865],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":210,"column":39,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":210,"endColumn":42,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[7925,7928],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[7925,7928],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":220,"column":38,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":220,"endColumn":41,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[8251,8254],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[8251,8254],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":359,"column":66,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":359,"endColumn":69,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[15277,15280],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[15277,15280],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":365,"column":84,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":365,"endColumn":87,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[15708,15711],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[15708,15711],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":16,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { Dialog, Transition } from \"@headlessui/react\";\nimport { Fragment, useState, useMemo, useEffect } from \"react\";\nimport Image from \"next/image\";\nimport { X, AlertCircle } from \"lucide-react\";\nimport { ensureColorContrast } from \"@/lib/color-contrast\";\nimport {\n  calculateEffectiveQuantity,\n  checkSaleQuantityLimits,\n  resolveTierPrice,\n} from \"@/lib/price-tiers\";\n\ninterface ComboConfigModalProps {\n  isOpen: boolean;\n  onClose: () => void;\n  combo: any;\n  productData: any;\n  primaryColor: string;\n  onConfirm: (quantity: number, configuration: any) => void;\n}\n\nexport default function ComboConfigModal({\n  isOpen,\n  onClose,\n  combo,\n  productData,\n  primaryColor,\n  onConfirm,\n}: ComboConfigModalProps) {\n  const [quantity, setQuantity] = useState(1);\n  const [widthStr, setWidthStr] = useState(\"\");\n  const [heightStr, setHeightStr] = useState(\"\");\n  const [selectedOptions, setSelectedOptions] = useState<Record<string, string>>({});\n  const [error, setError] = useState(\"\");\n\n  // Reset state when modal opens\n  useEffect(() => {\n    if (isOpen) {\n      setQuantity(1);\n      setWidthStr(\"\");\n      setHeightStr(\"\");\n      setSelectedOptions({});\n      setError(\"\");\n    }\n  }, [isOpen, productData]);\n\n  // Initialize minimum quantity based on product settings\n  useEffect(() => {\n    if (isOpen && productData) {\n      let initialQty = 1;\n      if (productData.allowed_sale_qty_enabled && Array.isArray(productData.allowed_sale_quantities) && productData.allowed_sale_quantities.length > 0) {\n        initialQty = Math.min(...productData.allowed_sale_quantities.map(Number));\n      } else if (productData.min_sale_qty_enabled && productData.min_sale_qty) {\n        initialQty = productData.min_sale_qty;\n      }\n      setQuantity(initialQty);\n    }\n  }, [isOpen, productData]);\n\n  // Calculate effective quantity for validation and pricing\n  const effectiveQty = useMemo(() => {\n    if (!productData) return quantity;\n    return calculateEffectiveQuantity({\n      quantity,\n      unit_type: productData.unit_type,\n      width_m: (productData.unit_type === \"square_meter\" || productData.unit_type === \"linear_meter\") ? parseFloat(widthStr.replace(\",\", \".\")) : undefined,\n      height_m: productData.unit_type === \"square_meter\" ? parseFloat(heightStr.replace(\",\", \".\")) : undefined,\n    });\n  }, [quantity, productData, widthStr, heightStr]);\n\n  // Quantity Limits Check\n  const quantityCheck = useMemo(() => {\n    if (!productData) return { isValid: true, isBelowMin: false, isAboveMax: false, isNotAllowedValue: false, minQty: null, maxQty: null, allowedQuantities: [] };\n    return checkSaleQuantityLimits({\n      min_sale_qty_enabled: productData.min_sale_qty_enabled,\n      min_sale_qty: productData.min_sale_qty,\n      max_sale_qty_enabled: productData.max_sale_qty_enabled,\n      max_sale_qty: productData.max_sale_qty,\n      allowed_sale_qty_enabled: productData.allowed_sale_qty_enabled,\n      allowed_sale_quantities: productData.allowed_sale_quantities,\n      effectiveQuantity: effectiveQty,\n    });\n  }, [productData, effectiveQty]);\n\n  const isSquareMeter =\n    productData?.unit_type === \"square_meter\" || productData?.unit_type === \"linear_meter\";\n  const hasVariations =\n    Array.isArray(productData?.product_attributes) && productData.product_attributes.length > 0;\n  \n  const parsedWidth = parseFloat(widthStr.replace(\",\", \".\"));\n  const parsedHeight = parseFloat(heightStr.replace(\",\", \".\"));\n  const isValidDims = isSquareMeter\n    ? (productData?.unit_type === \"square_meter\" ? (parsedWidth > 0 && parsedHeight > 0) : parsedWidth > 0)\n    : true;\n\n  // Price resolution (including tiers)\n  const resolvedPriceInfo = useMemo(() => {\n    if (!productData) return { unitPrice: 0, totalPrice: 0, hasTierApplied: false };\n    let perQuantityAdjustment = 0;\n    let oneTimeAdjustment = 0;\n    if (hasVariations) {\n      productData.product_attributes.forEach((attr: any) => {\n        const selectedLabel = selectedOptions[attr.name];\n        if (selectedLabel && Array.isArray(attr.options)) {\n          const opt = attr.options.find((o: any) => (typeof o === 'string' ? o : o.label) === selectedLabel);\n          if (opt && typeof opt !== 'string' && opt.price) {\n            if (opt.price_applies_per_quantity === false) {\n              oneTimeAdjustment += Number(opt.price);\n            } else {\n              perQuantityAdjustment += Number(opt.price);\n            }\n          }\n        }\n      });\n    }\n\n    const safeEffectiveQty = effectiveQty > 0 ? effectiveQty : 1;\n    const optionsUnitAdjustment = perQuantityAdjustment + oneTimeAdjustment / safeEffectiveQty;\n    const tierResolution = resolveTierPrice(Number(productData.price) || 0, effectiveQty, productData.product_price_tiers || []);\n\n    let finalUnitPrice = productData.use_variation_price_only\n      ? Math.max(0, optionsUnitAdjustment)\n      : Math.max(0, tierResolution.unitPrice + optionsUnitAdjustment);\n    if (combo && combo.discount_type === \"percentage\") {\n      finalUnitPrice = finalUnitPrice * (1 - combo.discount_value / 100);\n    } else if (combo && combo.discount_type === \"fixed\") {\n      finalUnitPrice = Math.max(0, finalUnitPrice - combo.discount_value);\n    }\n\n    return {\n      unitPrice: finalUnitPrice,\n      totalPrice: finalUnitPrice * effectiveQty,\n      hasTierApplied: !!tierResolution.tier\n    };\n  }, [productData, selectedOptions, effectiveQty, combo, hasVariations]);\n\n  if (!isOpen || !combo || !productData) return null;\n\n\n  const buttonTextColor = ensureColorContrast(primaryColor, \"#FFFFFF\", {\n    dark: \"#111827\",\n    light: \"#FFFFFF\",\n    minRatio: 2.5,\n  });\n\n  const handleConfirm = () => {\n    setError(\"\");\n\n    // Validate Dimensions\n    if (isSquareMeter && !isValidDims) {\n      setError(\"Por favor, preencha as medidas corretamente.\");\n      return;\n    }\n\n    // Validate Variations\n    if (hasVariations) {\n      const requiredAttributes = productData.product_attributes.filter((attr: any) => attributeIsRequired(attr));\n      for (const attr of requiredAttributes) {\n        if (!selectedOptions[attr.name]) {\n          setError(`Selecione uma opção para: ${attr.name}`);\n          return;\n        }\n      }\n    }\n\n    // Validate Quantity Rules\n    if (!quantityCheck.isValid) {\n      if (quantityCheck.isBelowMin) {\n        setError(`Quantidade mínima para este produto é ${quantityCheck.minQty}.`);\n      } else if (quantityCheck.isAboveMax) {\n        setError(`Quantidade máxima para este produto é ${quantityCheck.maxQty}.`);\n      } else if (quantityCheck.isNotAllowedValue) {\n        setError(`Quantidade não permitida. Opções: ${quantityCheck.allowedQuantities.join(\", \")}`);\n      } else {\n        setError(\"Quantidade inválida para as regras deste produto.\");\n      }\n      return;\n    }\n\n    // Build configuration object\n    const configuration: any = {\n      unit_type: productData.unit_type,\n    };\n\n    if (isSquareMeter) {\n      configuration.width_m = parsedWidth;\n      configuration.height_m = productData.unit_type === \"square_meter\" ? parsedHeight : 1;\n    }\n\n    if (hasVariations) {\n      const descParts = Object.entries(selectedOptions).map(([k, v]) => `${k}: ${v}`);\n      configuration.variation = {\n        id: \"combo-variation\",\n        description: descParts.join(\" / \"),\n        selected_options: {\n          options: Object.entries(selectedOptions).map(([k, v]) => ({\n            attribute_name: k,\n            label: v,\n            price:\n              Number(\n                productData.product_attributes\n                  ?.find((attr: any) => attr.name === k)\n                  ?.options?.find((opt: any) => (typeof opt === \"string\" ? opt === v : opt.label === v) && typeof opt !== \"string\")\n                  ?.price\n              ) || 0,\n            price_applies_per_quantity:\n              productData.product_attributes\n                ?.find((attr: any) => attr.name === k)\n                ?.options?.find((opt: any) => (typeof opt === \"string\" ? opt === v : opt.label === v) && typeof opt !== \"string\")\n                ?.price_applies_per_quantity !== false,\n          }))\n        }\n      };\n    }\n\n    onConfirm(quantity, { ...configuration, resolvedUnitPrice: resolvedPriceInfo.unitPrice });\n  };\n\n  function attributeIsRequired(attr: any) {\n    if (attr.is_required !== undefined) return attr.is_required;\n    return true; // default to true if not specified\n  }\n\n  const formatMoney = (val: number) =>\n    new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(val);\n\n  return (\n    <Transition appear show={isOpen} as={Fragment}>\n      <Dialog as=\"div\" className=\"relative z-50\" onClose={onClose}>\n        <Transition.Child\n          as={Fragment}\n          enter=\"ease-out duration-300\"\n          enterFrom=\"opacity-0\"\n          enterTo=\"opacity-100\"\n          leave=\"ease-in duration-200\"\n          leaveFrom=\"opacity-100\"\n          leaveTo=\"opacity-0\"\n        >\n          <div className=\"fixed inset-0 bg-black/40 backdrop-blur-sm\" />\n        </Transition.Child>\n\n        <div className=\"fixed inset-0 overflow-y-auto\">\n          <div className=\"flex min-h-full items-center justify-center p-4 text-center\">\n            <Transition.Child\n              as={Fragment}\n              enter=\"ease-out duration-300\"\n              enterFrom=\"opacity-0 scale-95\"\n              enterTo=\"opacity-100 scale-100\"\n              leave=\"ease-in duration-200\"\n              leaveFrom=\"opacity-100 scale-100\"\n              leaveTo=\"opacity-0 scale-95\"\n            >\n              <Dialog.Panel className=\"w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all\">\n                <div className=\"flex justify-between items-start mb-5\">\n                  <Dialog.Title as=\"h3\" className=\"text-lg font-bold leading-6 text-gray-900 pr-8\">\n                    Configurar {productData.name}\n                  </Dialog.Title>\n                  <button\n                    onClick={onClose}\n                    className=\"rounded-full p-1 hover:bg-gray-100 transition-colors absolute top-5 right-5 text-gray-400\"\n                  >\n                    <X size={20} />\n                  </button>\n                </div>\n\n                {/* Product Info Summary */}\n                <div className=\"flex gap-4 mb-6 bg-gray-50 p-3 rounded-xl border border-gray-100\">\n                  {productData.image_url && (\n                    <div className=\"w-16 h-16 relative rounded-lg overflow-hidden shrink-0 bg-white border border-gray-200\">\n                      <Image src={productData.image_url} alt={productData.name} fill className=\"object-cover\" unoptimized />\n                    </div>\n                  )}\n                  <div>\n                    <div className=\"text-sm font-medium text-gray-900 mb-1\">{combo.title || \"Oferta Combo\"}</div>\n                    <div className=\"flex items-center gap-2\">\n                      <span className=\"font-bold text-emerald-600\">\n                        {formatMoney(resolvedPriceInfo.unitPrice)}\n                      </span>\n                      {combo.discount_type !== \"none\" && (\n                         <span className=\"text-xs text-gray-400 line-through\">{formatMoney(productData.price)}</span>\n                      )}\n                      {resolvedPriceInfo.hasTierApplied && (\n                        <span className=\"text-[10px] bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded font-bold\">Atacado aplicado</span>\n                      )}\n                    </div>\n                  </div>\n                </div>\n\n                <div className=\"space-y-6\">\n                  {/* Quantity Control */}\n                  <div>\n                    <h4 className=\"text-sm font-semibold text-gray-900 mb-3\">Quantidade</h4>\n                    <div className=\"flex items-center justify-between bg-white border border-gray-200 rounded-lg p-1.5 w-max\">\n                      <button\n                        type=\"button\"\n                        onClick={() => setQuantity(Math.max(1, quantity - 1))}\n                        className=\"w-10 h-10 flex items-center justify-center text-gray-500 hover:text-gray-900 hover:bg-gray-50 rounded-md transition-colors\"\n                      >\n                        -\n                      </button>\n                      <input\n                        type=\"number\"\n                        value={quantity}\n                        onChange={(e) => {\n                          let val = parseInt(e.target.value, 10);\n                          if (isNaN(val) || val < 1) val = 1;\n                          setQuantity(val);\n                        }}\n                        className=\"w-16 h-10 text-center font-bold text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary/20 bg-transparent border-none p-0 hide-arrows\"\n                        min=\"1\"\n                      />\n                      <button\n                        type=\"button\"\n                        onClick={() => setQuantity(quantity + 1)}\n                        className=\"w-10 h-10 flex items-center justify-center text-gray-500 hover:text-gray-900 hover:bg-gray-50 rounded-md transition-colors\"\n                      >\n                        +\n                      </button>\n                    </div>\n                  </div>\n\n                  {/* Dimensions (if square meter) */}\n                  {isSquareMeter && (\n                    <div>\n                      <h4 className=\"text-sm font-semibold text-gray-900 mb-3\">Medidas</h4>\n                      <div className=\"grid grid-cols-2 gap-4\">\n                        <div>\n                          <label className=\"block text-xs font-medium text-gray-500 mb-1\">LARGURA (M)</label>\n                          <input\n                            type=\"text\"\n                            inputMode=\"decimal\"\n                            placeholder=\"Ex: 1,20\"\n                            value={widthStr}\n                            onChange={(e) => setWidthStr(e.target.value)}\n                            className=\"w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n                          />\n                        </div>\n                        {productData.unit_type === \"square_meter\" && (\n                          <div>\n                            <label className=\"block text-xs font-medium text-gray-500 mb-1\">ALTURA (M)</label>\n                            <input\n                              type=\"text\"\n                              inputMode=\"decimal\"\n                              placeholder=\"Ex: 0,80\"\n                              value={heightStr}\n                              onChange={(e) => setHeightStr(e.target.value)}\n                              className=\"w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n                            />\n                          </div>\n                        )}\n                      </div>\n                    </div>\n                  )}\n\n                  {/* Variations */}\n                  {hasVariations && (\n                    <div className=\"space-y-4\">\n                      {productData.product_attributes.map((attr: any) => (\n                        <div key={attr.name}>\n                          <h4 className=\"text-sm font-semibold text-gray-900 mb-2\">\n                            {attr.name} {attributeIsRequired(attr) && <span className=\"text-red-500\">*</span>}\n                          </h4>\n                          <div className=\"flex flex-wrap gap-2\">\n                            {Array.isArray(attr.options) && attr.options.map((opt: any) => {\n                              const label = typeof opt === \"string\" ? opt : opt.label;\n                              const isSelected = selectedOptions[attr.name] === label;\n                              return (\n                                <button\n                                  key={label}\n                                  onClick={() => setSelectedOptions(prev => ({ ...prev, [attr.name]: label }))}\n                                  className={`px-3 py-1.5 text-sm rounded-lg border transition-all ${\n                                    isSelected \n                                      ? \"border-primary bg-primary/5 text-primary font-medium\" \n                                      : \"border-gray-200 text-gray-700 hover:border-gray-300 hover:bg-gray-50\"\n                                  }`}\n                                  style={isSelected ? { borderColor: primaryColor, color: primaryColor } : {}}\n                                >\n                                  {label}\n                                </button>\n                              );\n                            })}\n                          </div>\n                        </div>\n                      ))}\n                    </div>\n                  )}\n                </div>\n\n                {error && (\n                  <div className=\"mt-4 p-3 bg-red-50 border border-red-100 rounded-lg flex items-start gap-2 text-sm text-red-600\">\n                    <AlertCircle size={16} className=\"shrink-0 mt-0.5\" />\n                    <p>{error}</p>\n                  </div>\n                )}\n\n                <div className=\"mt-8\">\n                  <button\n                    type=\"button\"\n                    className=\"w-full rounded-xl px-4 py-3.5 text-sm font-bold shadow-sm transition-all hover:opacity-90 active:scale-[0.98]\"\n                    style={{ backgroundColor: primaryColor, color: buttonTextColor }}\n                    onClick={handleConfirm}\n                  >\n                    Confirmar Seleção\n                  </button>\n                </div>\n              </Dialog.Panel>\n            </Transition.Child>\n          </div>\n        </div>\n      </Dialog>\n    </Transition>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\OrderStatusRefreshButton.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ProductCombosWidget.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'useCart' is defined but never used.","line":3,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":17,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"useCart"},"fix":{"range":[15,63],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Plus' is defined but never used.","line":4,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":14,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Plus"},"fix":{"range":[73,78],"text":""},"desc":"Remove unused variable \"Plus\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Percent' is defined but never used.","line":4,"column":37,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":44,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Percent"},"fix":{"range":[98,107],"text":""},"desc":"Remove unused variable \"Percent\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ArrowRight' is defined but never used.","line":4,"column":46,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":56,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"ArrowRight"},"fix":{"range":[107,119],"text":""},"desc":"Remove unused variable \"ArrowRight\"."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":35,"column":19,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":35,"endColumn":22,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[912,915],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[912,915],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":40,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":40,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[998,1001],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[998,1001],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":57,"column":68,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":57,"endColumn":71,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1668,1671],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1668,1671],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":142,"column":70,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":142,"endColumn":73,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[5194,5197],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[5194,5197],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'buttonTextColor' is assigned a value but never used.","line":184,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":184,"endColumn":24},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'showTotalPrice' is assigned a value but never used.","line":198,"column":17,"nodeType":"Identifier","messageId":"unusedVar","endLine":198,"endColumn":31}],"suppressedMessages":[],"errorCount":4,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useCart } from \"@/context/CartContext\";\nimport { Plus, Check, ShoppingCart, Percent, ArrowRight } from \"lucide-react\";\nimport Image from \"next/image\";\nimport { useState } from \"react\";\nimport { toast } from \"sonner\";\nimport { ensureColorContrast } from \"@/lib/color-contrast\";\nimport { getSupabaseClient } from \"@/lib/supabase\";\nimport ComboConfigModal from \"./ComboConfigModal\";\n\ninterface ComboProduct {\n  id: string;\n  name: string;\n  price: number;\n  image_url: string | null;\n  price_on_request: boolean;\n  use_variation_price_only: boolean;\n  user_id?: string;\n}\n\ninterface ProductCombo {\n  id: string;\n  title: string;\n  discount_type: \"percentage\" | \"fixed\" | \"none\";\n  discount_value: number;\n  main_product_id: string;\n  combo_product: ComboProduct;\n}\n\nexport interface SelectedComboData {\n  combo: ProductCombo;\n  quantity: number;\n  discountedPrice: number;\n  configuration?: any;\n}\n\ninterface ProductCombosWidgetProps {\n  combos: ProductCombo[];\n  mainProduct: any;\n  primaryColor: string;\n  onComboSelect?: (selectedCombos: SelectedComboData[]) => void;\n}\n\nexport default function ProductCombosWidget({ \n  combos, \n  mainProduct,\n  primaryColor,\n  onComboSelect\n}: ProductCombosWidgetProps) {\n  const [selectedCombos, setSelectedCombos] = useState<Record<string, SelectedComboData>>({});\n  \n  // State for complex product modal\n  const [configModalOpen, setConfigModalOpen] = useState(false);\n  const [loadingConfig, setLoadingConfig] = useState<string | null>(null);\n  const [activeComboForConfig, setActiveComboForConfig] = useState<ProductCombo | null>(null);\n  const [fullComboProductData, setFullComboProductData] = useState<any>(null);\n\n  if (!combos || combos.length === 0) return null;\n\n  const formatMoney = (val: number) =>\n    new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(val);\n\n  const isMainProductDynamic = \n    mainProduct.price_on_request || \n    mainProduct.use_variation_price_only || \n    mainProduct.unit_type === \"square_meter\" || \n    mainProduct.unit_type === \"linear_meter\";\n\n  const calculateDiscountedPrice = (combo: ProductCombo) => {\n    const originalPrice = combo.combo_product.price;\n    if (combo.discount_type === \"percentage\") {\n      return originalPrice - (originalPrice * combo.discount_value / 100);\n    } else if (combo.discount_type === \"fixed\") {\n      return Math.max(0, originalPrice - combo.discount_value);\n    }\n    return originalPrice;\n  };\n\n  const handleToggleCombo = async (combo: ProductCombo) => {\n    if (combo.combo_product.price_on_request) {\n      toast.error(\"O produto sugerido está sob consulta e não pode ser adicionado diretamente.\");\n      return;\n    }\n\n    if (selectedCombos[combo.id]) {\n      // Deselect\n      const newSelected = { ...selectedCombos };\n      delete newSelected[combo.id];\n      setSelectedCombos(newSelected);\n      if (onComboSelect) onComboSelect(Object.values(newSelected));\n    } else {\n      // Check if product is complex (requires configuration) or has complex pricing rules\n      setLoadingConfig(combo.id);\n      try {\n        const supabase = getSupabaseClient();\n        const { data, error } = await supabase\n          .from(\"products\")\n          .select(`\n            *,\n            product_attributes (\n              id, name, options, position, is_required, allow_multiple\n            ),\n            product_variations (\n              id, combination, price, stock, sku\n            ),\n            product_price_tiers (*)\n          `)\n          .eq(\"id\", combo.combo_product.id)\n          .single();\n\n        if (error || !data) throw error || new Error(\"Produto não encontrado\");\n\n        const hasVariations = data.product_attributes && data.product_attributes.length > 0;\n        const isSquareMeter = data.unit_type === \"square_meter\" || data.unit_type === \"linear_meter\";\n        const hasPricingTiers = data.product_price_tiers && data.product_price_tiers.length > 0;\n        const hasQuantityLimits = data.min_sale_qty_enabled || data.max_sale_qty_enabled || data.allowed_sale_qty_enabled;\n\n        // Open modal if it needs configuration OR if it has complex quantity/pricing rules\n        // so the user can see the final price based on the rules.\n        if (hasVariations || isSquareMeter || hasPricingTiers || hasQuantityLimits) {\n          setFullComboProductData(data);\n          setActiveComboForConfig(combo);\n          setConfigModalOpen(true);\n        } else {\n          // Simple product, just select it\n          const discountedPrice = calculateDiscountedPrice(combo);\n          const comboData: SelectedComboData = { combo, quantity: 1, discountedPrice, configuration: { unit_type: data.unit_type } };\n          const newSelected = { ...selectedCombos, [combo.id]: comboData };\n          setSelectedCombos(newSelected);\n          if (onComboSelect) onComboSelect(Object.values(newSelected));\n        }\n      } catch (err) {\n        console.error(\"Error fetching combo product details\", err);\n        toast.error(\"Erro ao carregar detalhes do produto do combo.\");\n      } finally {\n        setLoadingConfig(null);\n      }\n    }\n  };\n\n  const handleConfigModalConfirm = (quantity: number, configuration: any) => {\n    if (!activeComboForConfig) return;\n    \n    // Use the resolved price from the modal if it exists (it accounts for tiers and combo discounts)\n    // Otherwise fallback to basic discount calculation\n    const discountedPrice = configuration?.resolvedUnitPrice ?? calculateDiscountedPrice(activeComboForConfig);\n    const comboData: SelectedComboData = { combo: activeComboForConfig, quantity, discountedPrice, configuration };\n    \n    const newSelected = { ...selectedCombos, [activeComboForConfig.id]: comboData };\n    setSelectedCombos(newSelected);\n    \n    if (onComboSelect) {\n      onComboSelect(Object.values(newSelected));\n    }\n    \n    setConfigModalOpen(false);\n  };\n\n  const updateQuantity = (combo: ProductCombo, newQty: number) => {\n    const discountedPrice = selectedCombos[combo.id]?.discountedPrice ?? calculateDiscountedPrice(combo);\n    const comboData = { \n      ...selectedCombos[combo.id], \n      quantity: newQty, \n      discountedPrice \n    };\n    const newSelected = { ...selectedCombos, [combo.id]: comboData };\n    setSelectedCombos(newSelected);\n    if (onComboSelect) onComboSelect(Object.values(newSelected));\n  };\n\n  const handleQuantityChange = (combo: ProductCombo, increment: boolean) => {\n    const currentQty = selectedCombos[combo.id]?.quantity || 1;\n    const newQty = increment ? currentQty + 1 : Math.max(1, currentQty - 1);\n    updateQuantity(combo, newQty);\n  };\n\n  const handleManualQuantityChange = (combo: ProductCombo, e: React.ChangeEvent<HTMLInputElement>) => {\n    let val = parseInt(e.target.value, 10);\n    if (isNaN(val) || val < 1) val = 1;\n    updateQuantity(combo, val);\n  };\n\n  const buttonTextColor = ensureColorContrast(primaryColor, \"#FFFFFF\", {\n    dark: \"#111827\",\n    light: \"#FFFFFF\",\n    minRatio: 2.5,\n  });\n\n  return (\n    <section className=\"mb-2\">\n      <div className=\"space-y-3\">\n        {combos.map((combo) => {\n          const isComboProductDynamic = \n            combo.combo_product.price_on_request || \n            combo.combo_product.use_variation_price_only;\n\n          const showTotalPrice = !isMainProductDynamic && !isComboProductDynamic;\n\n          const comboOriginalPrice = combo.combo_product.price;\n          const comboDiscountedPrice = calculateDiscountedPrice(combo);\n          \n          const isSelected = !!selectedCombos[combo.id];\n          const selectedComboData = selectedCombos[combo.id];\n          const currentQty = isSelected ? selectedCombos[combo.id].quantity : 1;\n          const currentConfig = isSelected ? selectedCombos[combo.id].configuration : null;\n          const currentUnitPrice = isSelected\n            ? (Number(selectedComboData?.discountedPrice) || comboDiscountedPrice)\n            : comboDiscountedPrice;\n          const hasConfiguredPriceChange =\n            isSelected &&\n            currentConfig &&\n            Math.abs(currentUnitPrice - comboDiscountedPrice) > 0.000001;\n          \n          // Multiply by quantity if selected\n          const displayOriginalPrice = isSelected ? comboOriginalPrice * currentQty : comboOriginalPrice;\n          const displayDiscountedPrice = currentUnitPrice * currentQty;\n          const displayBaseComboPrice = comboDiscountedPrice * currentQty;\n\n          return (\n            <div key={combo.id} className=\"flex flex-col\">\n              {combo.title && (\n                <div className=\"text-sm font-bold text-gray-800 mb-2 pl-1\">{combo.title}</div>\n              )}\n              <div \n                className={`rounded-xl border p-2.5 sm:p-3 shadow-sm relative overflow-hidden transition-all duration-200 cursor-pointer ${\n                  isSelected ? 'border-primary bg-primary/5 ring-1 ring-primary' : 'border-gray-200 bg-white hover:border-primary/50'\n                }`}\n                style={isSelected ? { borderColor: primaryColor } : {}}\n                onClick={(e) => {\n                  // Prevent toggle if clicking on quantity buttons\n                  if ((e.target as HTMLElement).closest('.qty-btn')) return;\n                  handleToggleCombo(combo);\n                }}\n              >\n              <div className=\"flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3\">\n                <div className=\"flex items-center gap-2.5 sm:gap-3 min-w-0 w-full\">\n                  {/* Checkbox & Images */}\n                  <div className=\"flex items-center gap-2.5 sm:gap-3 shrink-0\">\n                    <div className={`w-5 h-5 rounded-md border flex items-center justify-center shrink-0 transition-colors ${\n                      isSelected ? 'border-transparent text-white' : 'border-gray-300 bg-white'\n                    }`}\n                    style={isSelected ? { backgroundColor: primaryColor } : {}}\n                    >\n                      {loadingConfig === combo.id ? (\n                        <div className=\"w-3 h-3 border-2 border-primary border-t-transparent rounded-full animate-spin\" style={isSelected ? { borderColor: 'white', borderTopColor: 'transparent' } : {}} />\n                      ) : isSelected ? (\n                        <Check size={14} strokeWidth={3} />\n                      ) : null}\n                    </div>\n\n                    <div className=\"w-12 h-12 sm:w-14 sm:h-14 relative rounded-lg border border-gray-100 overflow-hidden bg-gray-50 flex-shrink-0\">\n                      {combo.combo_product.image_url ? (\n                        <Image src={combo.combo_product.image_url} alt={combo.combo_product.name} fill className=\"object-cover\" unoptimized />\n                      ) : (\n                        <div className=\"w-full h-full flex items-center justify-center bg-gray-100 text-gray-400\">\n                          <ShoppingCart size={16} />\n                        </div>\n                      )}\n                    </div>\n                  </div>\n\n                  {/* Details */}\n                  <div className=\"flex-1 min-w-0 text-left\">\n                  <div className=\"font-medium text-gray-900 leading-tight mb-0.5 text-[13px] sm:text-sm\">\n                    <span className=\"text-gray-500 font-normal mr-1\">Leve também:</span>\n                    {combo.combo_product.name}\n                  </div>\n                  \n                  {isSelected && currentConfig && (\n                    <div className=\"text-[10px] sm:text-xs text-gray-500 mb-1 flex flex-col gap-0.5\">\n                      {currentConfig.width_m && (\n                        <span>Medidas: {currentConfig.width_m}m {currentConfig.unit_type === 'square_meter' ? `x ${currentConfig.height_m}m` : ''}</span>\n                      )}\n                      {currentConfig.variation?.description && (\n                        <span>{currentConfig.variation.description}</span>\n                      )}\n                    </div>\n                  )}\n\n                  <div className=\"flex items-center gap-1 sm:gap-1.5 flex-wrap\">\n                    <span className=\"text-[13px] sm:text-sm font-bold text-emerald-600\">{formatMoney(displayDiscountedPrice)}</span>\n                    {!hasConfiguredPriceChange && displayOriginalPrice > displayDiscountedPrice && (\n                      <span className=\"text-[10px] sm:text-xs text-gray-400 line-through\">{formatMoney(displayOriginalPrice)}</span>\n                    )}\n                    {hasConfiguredPriceChange && (\n                      <span className=\"text-[10px] sm:text-xs text-gray-400 line-through\">Antes: {formatMoney(displayBaseComboPrice)}</span>\n                    )}\n                    {combo.discount_type === \"percentage\" && (\n                      <span className=\"text-[9px] sm:text-[10px] font-bold bg-red-100 text-red-600 px-1.5 py-0.5 rounded leading-none\">\n                        -{combo.discount_value}% OFF\n                      </span>\n                    )}\n                    {hasConfiguredPriceChange && (\n                      <span className=\"text-[9px] sm:text-[10px] font-bold bg-emerald-100 text-emerald-700 px-1.5 py-0.5 rounded leading-none\">\n                        Preço atualizado\n                      </span>\n                    )}\n                  </div>\n                </div>\n                </div>\n\n                {/* Quantity Control (Only show if selected) */}\n                {isSelected && (\n                  <div className=\"w-full sm:w-auto flex justify-center sm:justify-end mt-1 sm:mt-0 qty-btn shrink-0\">\n                    <div className=\"flex items-center border border-gray-200 rounded-lg bg-white h-7 sm:h-9\">\n                      <button\n                        type=\"button\"\n                        onClick={(e) => { e.stopPropagation(); handleQuantityChange(combo, false); }}\n                        className=\"w-7 sm:w-9 h-full flex items-center justify-center text-gray-500 hover:text-gray-900 hover:bg-gray-50 rounded-l-lg transition-colors\"\n                      >\n                        -\n                      </button>\n                      <input\n                        type=\"number\"\n                        value={currentQty}\n                        onChange={(e) => handleManualQuantityChange(combo, e)}\n                        onClick={(e) => e.stopPropagation()}\n                        className=\"w-8 sm:w-10 h-full text-center text-xs sm:text-sm font-medium text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary/20 bg-transparent border-none p-0 hide-arrows\"\n                        min=\"1\"\n                      />\n                      <button\n                        type=\"button\"\n                        onClick={(e) => { e.stopPropagation(); handleQuantityChange(combo, true); }}\n                        className=\"w-7 sm:w-9 h-full flex items-center justify-center text-gray-500 hover:text-gray-900 hover:bg-gray-50 rounded-r-lg transition-colors\"\n                      >\n                        +\n                      </button>\n                    </div>\n                  </div>\n                )}\n              </div>\n              </div>\n            </div>\n          );\n        })}\n      </div>\n      \n      {/* Quick Add Modal for Complex Combo Products */}\n      <ComboConfigModal \n        isOpen={configModalOpen}\n        onClose={() => setConfigModalOpen(false)}\n        combo={activeComboForConfig}\n        productData={fullComboProductData}\n        primaryColor={primaryColor}\n        onConfirm={handleConfigModalConfirm}\n      />\n    </section>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ProductMobileNavShell.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":39,"column":19,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":39,"endColumn":22,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[1075,1078],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[1075,1078],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { Search, ShoppingBag, X, ChevronDown } from \"lucide-react\";\nimport CatalogMobileBottomNav from \"@/components/CatalogMobileBottomNav\";\nimport { getSupabaseClient } from \"@/lib/supabase\";\nimport { withAlpha } from \"@/lib/color-contrast\";\nimport { normalizeWhatsappForLink } from \"@/lib/phone\";\n\ntype MobileCategory = {\n  id: string;\n  name: string;\n  slug?: string | null;\n  parent_id?: string | null;\n};\n\ntype SearchProduct = {\n  id: string;\n  name: string;\n  image_url?: string | null;\n  price?: number | null;\n  price_on_request?: boolean | null;\n};\n\ntype ProductMobileNavShellProps = {\n  slug: string;\n  profileId: string;\n  primaryColor: string;\n  logoUrl?: string | null;\n  storeName: string;\n  storeSlogan?: string | null;\n  whatsappDigits?: string;\n  categories: MobileCategory[];\n  announcementCouponId?: string | null;\n  initialSelectedCategoryId?: string | null;\n  storiesGroups?: any[];\n  catalogStoriesEnabled?: boolean;\n};\n\nexport default function ProductMobileNavShell({\n  slug,\n  profileId,\n  primaryColor,\n  logoUrl,\n  storeName,\n  storeSlogan,\n  whatsappDigits,\n  categories,\n}: ProductMobileNavShellProps) {\n  const [isMobileSearchOpen, setIsMobileSearchOpen] = useState(false);\n  const [isMobileCategoriesOpen, setIsMobileCategoriesOpen] = useState(false);\n  const [expandedMobileParentIds, setExpandedMobileParentIds] = useState<Set<string>>(new Set());\n  const [searchTerm, setSearchTerm] = useState(\"\");\n  const [searchProducts, setSearchProducts] = useState<SearchProduct[]>([]);\n  const [searchLoaded, setSearchLoaded] = useState(false);\n\n  const rootCategories = useMemo(\n    () => categories.filter((category) => !category.parent_id),\n    [categories]\n  );\n  const subcategoriesByParent = useMemo(() => {\n    const map = new Map<string, MobileCategory[]>();\n    categories.forEach((category) => {\n      if (!category.parent_id) return;\n      const list = map.get(category.parent_id) || [];\n      list.push(category);\n      map.set(category.parent_id, list);\n    });\n    return map;\n  }, [categories]);\n\n  const filteredProducts = useMemo(() => {\n    const term = searchTerm.trim().toLowerCase();\n    if (!term) return searchProducts.slice(0, 8);\n    return searchProducts\n      .filter((product) => product.name.toLowerCase().includes(term))\n      .slice(0, 12);\n  }, [searchProducts, searchTerm]);\n\n  const loadSearchProducts = async () => {\n    if (searchLoaded) return;\n    const supabase = getSupabaseClient();\n    const { data } = await supabase\n      .from(\"products\")\n      .select(\"id,name,image_url,price,price_on_request\")\n      .eq(\"user_id\", profileId)\n      .eq(\"active\", true)\n      .eq(\"show_in_catalog\", true)\n      .order(\"catalog_position\", { ascending: true, nullsFirst: false })\n      .order(\"created_at\", { ascending: false })\n      .limit(120);\n    const products = (data || []) as SearchProduct[];\n    setSearchProducts(products);\n    setSearchLoaded(true);\n  };\n\n  const toggleMobileParentCategory = (categoryId: string) => {\n    setExpandedMobileParentIds((prev) => {\n      const next = new Set(prev);\n      if (next.has(categoryId)) {\n        next.delete(categoryId);\n      } else {\n        next.add(categoryId);\n      }\n      return next;\n    });\n  };\n\n  const closeCategoryMenus = () => {\n    setIsMobileCategoriesOpen(false);\n    setExpandedMobileParentIds(new Set());\n  };\n\n  const mobileHoverBg = withAlpha(primaryColor, 0.06);\n  const mobileSubmenuLine = withAlpha(primaryColor, 0.2);\n  const resolvedWhatsappDigits = normalizeWhatsappForLink(whatsappDigits);\n\n  const formatMoney = (value: number) =>\n    new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(value);\n\n  return (\n    <>\n      <CatalogMobileBottomNav\n        slug={slug}\n        primaryColor={primaryColor}\n        categoriesHref={`/${slug}`}\n        whatsappDigits={resolvedWhatsappDigits}\n        isSearchActive={isMobileSearchOpen}\n        isCategoriesActive={isMobileCategoriesOpen}\n        onHomeClick={() => {\n          closeCategoryMenus();\n          setIsMobileSearchOpen(false);\n        }}\n        onSearchClick={() => {\n          setIsMobileCategoriesOpen(false);\n          setIsMobileSearchOpen(true);\n          void loadSearchProducts();\n        }}\n        onCategoriesClick={() => {\n          setIsMobileSearchOpen(false);\n          setIsMobileCategoriesOpen((prev) => !prev);\n        }}\n        />\n\n      {/* Mobile Search Overlay */}\n      {isMobileSearchOpen && (\n        <div className=\"md:hidden fixed inset-0 bg-white z-[80] pt-20 px-4 animate-in slide-in-from-bottom-10 duration-200\">\n          <button\n            onClick={() => setIsMobileSearchOpen(false)}\n            className=\"absolute top-5 right-4 p-2 rounded-full bg-gray-100\"\n          >\n            <X size={20} />\n          </button>\n          <div className=\"max-w-lg mx-auto space-y-4\">\n            <div className=\"relative\">\n              <Search className=\"absolute left-4 top-1/2 -translate-y-1/2 text-gray-400\" size={20} />\n              <input\n                type=\"text\"\n                value={searchTerm}\n                onChange={(event) => setSearchTerm(event.target.value)}\n                placeholder=\"Buscar produtos...\"\n                className=\"w-full h-14 pl-12 pr-4 rounded-2xl border border-gray-200 bg-gray-50 text-sm font-medium outline-none focus:ring-2 focus:ring-gray-200\"\n                autoFocus\n              />\n            </div>\n            <div className=\"space-y-2 max-h-[70vh] overflow-y-auto pb-20\">\n              {filteredProducts.map((product) => (\n                <Link\n                  key={product.id}\n                  href={`/${slug}/p/${product.id}`}\n                  onClick={() => setIsMobileSearchOpen(false)}\n                  className=\"flex items-center gap-4 p-3 bg-gray-50 rounded-xl border border-gray-100\"\n                >\n                  <div className=\"w-12 h-12 bg-white rounded-lg flex items-center justify-center shrink-0\">\n                    {product.image_url ? (\n                      <Image src={product.image_url} alt={product.name} width={48} height={48} className=\"w-full h-full object-cover rounded-lg\" />\n                    ) : (\n                      <ShoppingBag size={20} className=\"text-gray-300\" />\n                    )}\n                  </div>\n                  <div>\n                    <p className=\"font-medium text-gray-900 line-clamp-1\">{product.name}</p>\n                    <p className=\"text-sm text-gray-500 font-bold\">\n                      {product.price_on_request ? \"Sob consulta\" : formatMoney(Number(product.price || 0))}\n                    </p>\n                  </div>\n                </Link>\n              ))}\n              {!filteredProducts.length ? (\n                <div className=\"rounded-xl border border-gray-200 bg-gray-50 p-4 text-sm text-gray-500\">\n                  Nenhum produto encontrado.\n                </div>\n              ) : null}\n            </div>\n          </div>\n        </div>\n      )}\n\n      <AnimatePresence initial={false}>\n        {isMobileCategoriesOpen && (\n          <div className=\"md:hidden fixed inset-0 z-[80]\">\n            <motion.div\n              initial={{ opacity: 0 }}\n              animate={{ opacity: 1 }}\n              exit={{ opacity: 0 }}\n              transition={{ duration: 0.22, ease: \"easeOut\" }}\n              className=\"absolute inset-0 bg-black/52\"\n              onClick={closeCategoryMenus}\n            />\n            <motion.aside\n              initial={{ x: \"-100%\" }}\n              animate={{ x: 0 }}\n              exit={{ x: \"-100%\" }}\n              transition={{ type: \"tween\", duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}\n              className=\"absolute left-0 top-0 bottom-0 w-[84%] max-w-[320px] bg-white rounded-r-3xl border-r border-slate-100 border-t border-slate-100 shadow-[12px_0_32px_rgba(15,23,42,0.25)] flex flex-col transform-gpu will-change-transform\"\n            >\n              <div className=\"px-5 pt-5 pb-3\">\n                <div className=\"flex items-start justify-between\">\n                  <div className=\"flex items-center gap-2.5 min-w-0\">\n                    {logoUrl ? (\n                      <div className=\"h-10 w-14 p-0.5 border shadow-sm shrink-0 rounded-md\" style={{ backgroundColor: withAlpha(primaryColor, 0.18), borderColor: withAlpha(primaryColor, 0.28) }}>\n                        <Image src={logoUrl} alt={storeName} width={56} height={40} className=\"h-full w-full object-contain\" />\n                      </div>\n                    ) : (\n                      <div className=\"h-9 w-9 rounded-lg bg-slate-50 border border-slate-100 shadow-sm flex items-center justify-center shrink-0\">\n                        <ShoppingBag size={18} className=\"text-slate-700\" />\n                      </div>\n                    )}\n                    <div className=\"min-w-0\">\n                      <span className=\"text-[13px] font-semibold text-slate-800 line-clamp-1 block\">{storeName}</span>\n                      {storeSlogan ? <span className=\"text-[11px] text-slate-500 line-clamp-1 block\">{storeSlogan}</span> : null}\n                    </div>\n                  </div>\n                  <button onClick={closeCategoryMenus} className=\"h-10 w-10 rounded-full bg-slate-100/90 text-slate-500 flex items-center justify-center\">\n                    <X size={17} />\n                  </button>\n                </div>\n                <div className=\"mt-4 text-[11px] uppercase tracking-[0.14em] font-bold text-slate-400\">Categorias</div>\n              </div>\n              <div className=\"h-px bg-slate-100\" />\n\n              <div className=\"flex-1 overflow-y-auto px-3 py-3 pb-[96px] space-y-1.5\">\n                <Link href={`/${slug}`} onClick={closeCategoryMenus} className=\"relative block rounded-xl px-3 py-2.5 text-left text-slate-700 hover:bg-slate-50 transition-all\">\n                  <span className=\"pl-3 inline-flex items-center gap-2 text-[13px] font-semibold\">\n                    <span>🛍️</span>\n                    <span>Todas os Produtos</span>\n                  </span>\n                </Link>\n\n                {rootCategories.map((category) => {\n                  const hasChildren = (subcategoriesByParent.get(category.id) || []).length > 0;\n                  const isExpanded = expandedMobileParentIds.has(category.id);\n                  return (\n                    <div key={category.id} className=\"space-y-1\">\n                      <div className=\"relative flex items-center rounded-xl px-3 py-2.5 text-[13px] text-slate-700\">\n                        <Link\n                          href={category.slug ? `/${slug}/c/${category.slug}` : `/${slug}`}\n                          onClick={closeCategoryMenus}\n                          className=\"pl-3 line-clamp-1 font-medium flex-1\"\n                        >\n                          {category.name}\n                        </Link>\n                        {hasChildren ? (\n                          <button\n                            type=\"button\"\n                            onClick={(event) => {\n                              event.preventDefault();\n                              event.stopPropagation();\n                              toggleMobileParentCategory(category.id);\n                            }}\n                            className=\"ml-2 h-6 w-6 rounded-full flex items-center justify-center\"\n                            style={isExpanded ? { backgroundColor: mobileHoverBg } : undefined}\n                          >\n                            <ChevronDown size={14} className={`text-slate-400 transition-transform ${isExpanded ? \"rotate-180\" : \"\"}`} />\n                          </button>\n                        ) : null}\n                      </div>\n                      {hasChildren && isExpanded ? (\n                        <div className=\"ml-5 pl-2 space-y-1 border-l\" style={{ borderColor: mobileSubmenuLine }}>\n                          {(subcategoriesByParent.get(category.id) || []).map((subcategory) => (\n                            <Link\n                              key={subcategory.id}\n                              href={subcategory.slug ? `/${slug}/c/${subcategory.slug}` : `/${slug}`}\n                              onClick={closeCategoryMenus}\n                              className=\"block rounded-lg px-3 py-2 text-[12px] text-slate-600 hover:text-slate-900 hover:bg-slate-50\"\n                            >\n                              <span className=\"inline-flex items-center gap-2\">\n                                <span className=\"text-[10px]\">•</span>\n                                <span>{subcategory.name}</span>\n                              </span>\n                            </Link>\n                          ))}\n                        </div>\n                      ) : null}\n                    </div>\n                  );\n                })}\n              </div>\n            </motion.aside>\n          </div>\n        )}\n      </AnimatePresence>\n    </>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ProductPageDesktopHeaderCart.tsx","messages":[{"ruleId":"react-hooks/set-state-in-effect","severity":2,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\nC:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ProductPageDesktopHeaderCart.tsx:18:5\n  16 |\n  17 |   useEffect(() => {\n> 18 |     setMounted(true);\n     |     ^^^^^^^^^^ Avoid calling setState() directly within an effect\n  19 |   }, []);\n  20 |\n  21 |   const showBadge = mounted && Number(cartCount) > 0 && items.length > 0;","line":18,"column":5,"nodeType":null,"endLine":18,"endColumn":15}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useCart } from \"@/context/CartContext\";\nimport { ShoppingBag } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nexport default function ProductPageDesktopHeaderCart({ \n  backControlColor, \n  isBackControlLight \n}: { \n  backControlColor: string;\n  isBackControlLight: boolean;\n}) {\n  const { toggleCart, cartCount, items } = useCart();\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const showBadge = mounted && Number(cartCount) > 0 && items.length > 0;\n\n  return (\n    <button\n      onClick={toggleCart}\n      className=\"inline-flex items-center justify-center w-10 h-10 rounded-full transition-all hover:bg-black/5 active:scale-95\"\n      style={{\n        color: backControlColor,\n        textShadow: isBackControlLight ? \"0 1px 2px rgba(0,0,0,0.35)\" : \"none\",\n      }}\n      aria-label=\"Abrir carrinho\"\n      title=\"Ver carrinho\"\n    >\n      <div className=\"relative\">\n        <ShoppingBag size={24} strokeWidth={2.5} />\n        {showBadge && (\n          <span className=\"absolute -top-1.5 -right-2 bg-red-500 text-white text-[10px] font-bold min-w-[18px] h-[18px] px-1 flex items-center justify-center rounded-full border-2 border-white\">\n            {cartCount}\n          </span>\n        )}\n      </div>\n    </button>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ProductPurchaseCard.tsx","messages":[{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":75,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":75,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2697,2700],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2697,2700],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":336,"column":56,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":336,"endColumn":59,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[13119,13122],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[13119,13122],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'currentPrice' is assigned a value but never used.","line":668,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":668,"endColumn":21},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":1386,"column":32,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":1386,"endColumn":35,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[59822,59825],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[59822,59825],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":3,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useCart } from \"@/context/CartContext\";\nimport { ShoppingBag, MessageCircle, Truck, ShieldCheck, CreditCard, Package, Clock, BadgeCheck, MapPin, Headphones, Hash } from \"lucide-react\";\nimport { normalizeWhatsappForLink } from \"@/lib/phone\";\nimport { ensureColorContrast, getReadableTextColor } from \"@/lib/color-contrast\";\nimport { toast } from \"sonner\";\nimport {\n  calculateEffectiveQuantity,\n  calculateTierDiscountPct,\n  checkSaleQuantityLimits,\n  getBestTierDiscount,\n  resolveTierPrice,\n  type ProductPriceTier,\n} from \"@/lib/price-tiers\";\n\ninterface ProductPurchaseCardProps {\n  product: {\n    id: string;\n    name: string;\n    price: number;\n    image_url?: string | null;\n    stock: number;\n    user_id: string;\n    unit_type?: string | null;\n    price_on_request?: boolean | null;\n    use_variation_price_only?: boolean | null;\n    product_price_tiers?: ProductPriceTier[] | null;\n    min_sale_qty_enabled?: boolean | null;\n    min_sale_qty?: number | null;\n    max_sale_qty_enabled?: boolean | null;\n    max_sale_qty?: number | null;\n    allowed_sale_qty_enabled?: boolean | null;\n    allowed_sale_quantities?: number[] | null;\n    track_inventory?: boolean | null;\n    inventory_scope?: \"product\" | \"option\" | \"variant\" | null;\n    inventory_attribute_name?: string | null;\n    product_images?: { id: string; image_url: string; sort_order?: number | null; is_primary?: boolean | null }[] | null;\n    product_attributes?: {\n      id: string;\n      name: string;\n      options: (string | {\n        id?: string;\n        label: string;\n        price: number;\n        price_applies_per_quantity?: boolean;\n        stock?: number;\n        is_required?: boolean;\n        position?: number;\n        image_id?: string | null;\n        minimum_stock?: number;\n        reorder_point?: number;\n      })[];\n      allow_multiple?: boolean | null;\n      is_required?: boolean | null;\n      position?: number | null;\n    }[] | null;\n    product_variations?: { id: string; combination: Record<string, string>; price: number | null; stock: number; sku?: string }[] | null;\n    custom_fields_config?: Array<{\n      id?: string | null;\n      title?: string | null;\n      field_type?: \"text\" | \"textarea\" | \"number\" | null;\n      placeholder?: string | null;\n      required?: boolean | null;\n      variation_signature?: string | null;\n      position?: number | null;\n      active?: boolean | null;\n    }> | null;\n  };\n  primaryColor: string;\n  whatsappNumber?: string | null;\n  onPreferredImageChange?: (imageId: string | null) => void;\n  trustItems?: Array<{ icon?: string | null; title?: string | null }> | null;\n  combosData?: any[];\n}\n\nfunction formatDiscountPct(value: number) {\n  const safeValue = Number(value) || 0;\n  return new Intl.NumberFormat(\"pt-BR\", {\n    maximumFractionDigits: safeValue >= 10 ? 0 : 1,\n    minimumFractionDigits: 0,\n  }).format(safeValue);\n}\n\nfunction formatTierThreshold(minQty: number, unitType: string) {\n  const qty = Number(minQty) || 0;\n  const qtyLabel = Number.isInteger(qty)\n    ? String(qty)\n    : new Intl.NumberFormat(\"pt-BR\", { maximumFractionDigits: 2 }).format(qty);\n\n  if (unitType === \"square_meter\") return `${qtyLabel} m²`;\n  if (unitType === \"meter\" || unitType === \"linear_meter\") return `${qtyLabel} m`;\n  return `${qtyLabel} unidades`;\n}\n\nfunction resolveAllowedQuantities(product: ProductPurchaseCardProps[\"product\"]) {\n  if (!product.allowed_sale_qty_enabled || !Array.isArray(product.allowed_sale_quantities)) return [];\n  const values = product.allowed_sale_quantities\n    .map((value) => Number(value))\n    .filter((value) => Number.isFinite(value) && value > 0)\n    .map((value) => Number(value.toFixed(3)));\n  if (!values.length) return [];\n  return Array.from(new Set(values)).sort((a, b) => a - b);\n}\n\nfunction resolveAdjacentAllowedQuantity(allowedValues: number[], currentValue: number, direction: -1 | 1) {\n  if (!allowedValues.length) return null;\n\n  if (direction < 0) {\n    for (let index = allowedValues.length - 1; index >= 0; index -= 1) {\n      if (allowedValues[index] < currentValue) return allowedValues[index];\n    }\n    return null;\n  }\n\n  for (let index = 0; index < allowedValues.length; index += 1) {\n    if (allowedValues[index] > currentValue) return allowedValues[index];\n  }\n  return null;\n}\n\nfunction formatQuantityValue(value: number) {\n  if (!Number.isFinite(value) || value <= 0) return \"0\";\n  const normalized = Number(value.toFixed(3));\n  return Number.isInteger(normalized)\n    ? String(normalized)\n    : new Intl.NumberFormat(\"pt-BR\", { minimumFractionDigits: 0, maximumFractionDigits: 3 }).format(normalized);\n}\n\nfunction getMinimumVariationUnitPrice(\n  attributes?: ProductPurchaseCardProps[\"product\"][\"product_attributes\"] | null\n) {\n  const list = Array.isArray(attributes) ? attributes : [];\n  if (!list.length) return 0;\n  const hasRequiredAttribute = list.some((attribute) => Boolean(attribute?.is_required));\n  const relevantAttributes = hasRequiredAttribute\n    ? list.filter((attribute) => Boolean(attribute?.is_required))\n    : list;\n  let total = 0;\n  for (const attribute of relevantAttributes) {\n    const options = Array.isArray(attribute?.options) ? attribute.options : [];\n    const optionPrices = options\n      .map((option) => (typeof option === \"string\" ? 0 : Number(option?.price) || 0))\n      .filter((value) => Number.isFinite(value) && value >= 0);\n    if (!optionPrices.length) continue;\n    total += Math.min(...optionPrices);\n  }\n  return Math.max(0, total);\n}\n\ntype NormalizedCustomField = {\n  id: string;\n  title: string;\n  field_type: \"text\" | \"textarea\" | \"number\";\n  placeholder: string;\n  required: boolean;\n  variation_signature: string | null;\n  position: number;\n};\n\nfunction normalizeVariationSignature(combination: Record<string, string> | null | undefined) {\n  if (!combination || typeof combination !== \"object\") return \"\";\n  return Object.entries(combination)\n    .map(([key, value]) => [String(key || \"\").trim().toLowerCase(), String(value || \"\").trim().toLowerCase()] as const)\n    .filter(([key, value]) => Boolean(key) && Boolean(value))\n    .sort((a, b) => a[0].localeCompare(b[0]))\n    .map(([key, value]) => `${key}:${value}`)\n    .join(\"|\");\n}\n\nfunction normalizeText(value: string) {\n  return String(value || \"\").trim().toLowerCase();\n}\n\nfunction matchesFieldVariationSignature(\n  fieldSignature: string,\n  params: {\n    selectedVariationSignature: string;\n    selectedOptions: Record<string, string[]>;\n  }\n) {\n  const signature = normalizeText(fieldSignature);\n  if (!signature) return true;\n\n  if (signature.startsWith(\"option::\")) {\n    const [, rawAttr, rawValue] = signature.split(\"::\");\n    const attr = normalizeText(rawAttr || \"\");\n    const value = normalizeText(rawValue || \"\");\n    if (!attr || !value) return false;\n    const selected = params.selectedOptions[Object.keys(params.selectedOptions).find((key) => normalizeText(key) === attr) || \"\"];\n    if (!Array.isArray(selected)) return false;\n    return selected.some((item) => normalizeText(item) === value);\n  }\n\n  return signature === normalizeText(params.selectedVariationSignature);\n}\n\nfunction buildOptionSignature(attributeName: string, optionLabel: string) {\n  return `option::${normalizeText(attributeName)}::${normalizeText(optionLabel)}`;\n}\n\nfunction colorWithAlpha(color: string, alpha: number) {\n  const safeAlpha = Math.max(0, Math.min(1, alpha));\n  const value = String(color || \"\").trim();\n  const hexMatch = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);\n  if (hexMatch) {\n    let hex = hexMatch[1];\n    if (hex.length === 3) {\n      hex = hex\n        .split(\"\")\n        .map((c) => c + c)\n        .join(\"\");\n    }\n    const r = Number.parseInt(hex.slice(0, 2), 16);\n    const g = Number.parseInt(hex.slice(2, 4), 16);\n    const b = Number.parseInt(hex.slice(4, 6), 16);\n    return `rgba(${r}, ${g}, ${b}, ${safeAlpha})`;\n  }\n  const rgbMatch = value.match(/^rgb\\(\\s*(\\d{1,3})\\s*[,\\s]\\s*(\\d{1,3})\\s*[,\\s]\\s*(\\d{1,3})\\s*\\)$/i);\n  if (rgbMatch) {\n    const [, r, g, b] = rgbMatch;\n    return `rgba(${r}, ${g}, ${b}, ${safeAlpha})`;\n  }\n  return `rgba(15, 23, 42, ${safeAlpha})`;\n}\n\nfunction normalizeCustomFieldsConfig(input: ProductPurchaseCardProps[\"product\"][\"custom_fields_config\"]) {\n  if (!Array.isArray(input)) return [] as NormalizedCustomField[];\n  return input\n    .map((field, index) => {\n      const title = String(field?.title || \"\").trim();\n      if (!title || field?.active === false) return null;\n      const id = String(field?.id || `cf_${index + 1}`).trim() || `cf_${index + 1}`;\n      const fieldType = field?.field_type === \"textarea\" || field?.field_type === \"number\" ? field.field_type : \"text\";\n      const variationSignature = String(field?.variation_signature || \"\").trim().toLowerCase();\n      return {\n        id,\n        title,\n        field_type: fieldType,\n        placeholder: String(field?.placeholder || \"\").trim(),\n        required: Boolean(field?.required),\n        variation_signature: variationSignature || null,\n        position: Number.isFinite(Number(field?.position)) ? Number(field?.position) : index,\n      } satisfies NormalizedCustomField;\n    })\n    .filter((field): field is NormalizedCustomField => Boolean(field))\n    .sort((a, b) => a.position - b.position);\n}\n\nimport ProductCombosWidget from \"@/components/ProductCombosWidget\";\n\nexport default function ProductPurchaseCard({ \n  product, \n  primaryColor,\n  whatsappNumber,\n  onPreferredImageChange,\n  trustItems,\n  combosData\n}: ProductPurchaseCardProps) {\n  const { addToCart } = useCart();\n  const allowedQuantities = useMemo(() => resolveAllowedQuantities(product), [product]);\n  const minimumQuantity = useMemo(() => {\n    if (allowedQuantities.length > 0) return Number(allowedQuantities[0].toFixed(3));\n    const minEnabled = Boolean(product.min_sale_qty_enabled);\n    const minQty = minEnabled ? Number(product.min_sale_qty) : 0;\n    if (!minEnabled || !Number.isFinite(minQty) || minQty <= 0) return 1;\n    return Number(minQty.toFixed(3));\n  }, [allowedQuantities, product.min_sale_qty, product.min_sale_qty_enabled]);\n  const [addError, setAddError] = useState(\"\");\n  const [quantity, setQuantity] = useState(minimumQuantity);\n  const [quantityInput, setQuantityInput] = useState(formatQuantityValue(minimumQuantity));\n  const [widthDigits, setWidthDigits] = useState(\"\");\n  const [heightDigits, setHeightDigits] = useState(\"\");\n  \n  // Variations State\n  const [selectedOptions, setSelectedOptions] = useState<Record<string, string[]>>({});\n  const [customFieldValues, setCustomFieldValues] = useState<Record<string, string>>({});\n  const hasVariations = product.product_attributes && product.product_attributes.length > 0;\n  const customFieldsConfig = useMemo(\n    () => normalizeCustomFieldsConfig(product.custom_fields_config),\n    [product.custom_fields_config]\n  );\n\n  // Calculate total price from selected options (Independent Options Model)\n  const orderedAttributes = useMemo(() => {\n    return (product.product_attributes || []).slice().sort((a, b) => (a.position ?? 0) - (b.position ?? 0));\n  }, [product.product_attributes]);\n\n  const requiredOptionsByAttr = useMemo(() => {\n    const map: Record<string, string[]> = {};\n    orderedAttributes.forEach((attr) => {\n      const required = (attr.options || [])\n        .map((opt) => (typeof opt === \"string\" ? { label: opt, is_required: false } : opt))\n        .filter((opt) => Boolean(opt.is_required))\n        .map((opt) => opt.label);\n      if (required.length) map[attr.name] = required;\n    });\n    return map;\n  }, [orderedAttributes]);\n\n  const effectiveSelectedOptions = useMemo(() => {\n    const next: Record<string, string[]> = {};\n    orderedAttributes.forEach((attr) => {\n      const required = requiredOptionsByAttr[attr.name] || [];\n      const userSelected = selectedOptions[attr.name] || [];\n      if (attr.allow_multiple) {\n        const merged = Array.from(new Set([...required, ...userSelected]));\n        if (merged.length) next[attr.name] = merged;\n      } else {\n        const chosen = userSelected[0] || required[0];\n        if (chosen) next[attr.name] = [chosen];\n      }\n    });\n    return next;\n  }, [orderedAttributes, requiredOptionsByAttr, selectedOptions]);\n\n  const selectedOptionImageId = useMemo(() => {\n    const availableImageIds = new Set((product.product_images || []).map((image) => image.id));\n    for (const attribute of orderedAttributes) {\n      const selected = effectiveSelectedOptions[attribute.name] || [];\n      if (!selected.length || !Array.isArray(attribute.options)) continue;\n      for (const selectedLabel of selected) {\n        const option = attribute.options.find(\n          (item) => (typeof item === \"string\" ? item : item.label) === selectedLabel\n        );\n        if (!option || typeof option === \"string\") continue;\n        if (option.image_id && availableImageIds.has(option.image_id)) {\n          return option.image_id;\n        }\n      }\n    }\n    return null;\n  }, [effectiveSelectedOptions, orderedAttributes, product.product_images]);\n\n  const [selectedCombos, setSelectedCombos] = useState<any[]>([]);\n\n  useEffect(() => {\n    if (!onPreferredImageChange) return;\n    onPreferredImageChange(selectedOptionImageId);\n  }, [onPreferredImageChange, selectedOptionImageId]);\n\n  const optionPriceBreakdown = useMemo(() => {\n    return orderedAttributes.reduce(\n      (acc, attr) => {\n        const selected = effectiveSelectedOptions[attr.name] || [];\n        if (!selected.length || !Array.isArray(attr.options)) return acc;\n        for (const label of selected) {\n          const option = attr.options.find((o) => (typeof o === \"string\" ? o : o.label) === label);\n          if (!option || typeof option === \"string\") continue;\n          const optionPrice = Number(option.price) || 0;\n          if (option.price_applies_per_quantity === false) {\n            acc.oneTime += optionPrice;\n          } else {\n            acc.perQuantity += optionPrice;\n          }\n        }\n        return acc;\n      },\n      { perQuantity: 0, oneTime: 0 }\n    );\n  }, [effectiveSelectedOptions, orderedAttributes]);\n  const minimumVariationUnitPrice = useMemo(\n    () => getMinimumVariationUnitPrice(product.product_attributes),\n    [product.product_attributes]\n  );\n  const trackInventory = product.track_inventory !== false;\n  const inventoryScope = product.inventory_scope || \"product\";\n  const inventoryAttributeName = (product.inventory_attribute_name || \"\").trim().toLowerCase();\n  const selectedOptionEntries = useMemo(() => {\n    return orderedAttributes.flatMap((attribute) => {\n      const selected = effectiveSelectedOptions[attribute.name] || [];\n      if (!selected.length) return [];\n      return selected\n        .map((selectedLabel) => {\n          const option = (attribute.options || []).find((item) => {\n            const label = typeof item === \"string\" ? item : item.label;\n            return label === selectedLabel;\n          });\n          if (!option || typeof option === \"string\") return null;\n          const optionId = typeof option.id === \"string\" ? option.id : null;\n          return {\n            option_id: optionId,\n            attribute_name: attribute.name,\n            label: option.label,\n            price: Number(option.price) || 0,\n            price_applies_per_quantity: option.price_applies_per_quantity !== false,\n            stock: Number.isFinite(Number(option.stock)) ? Number(option.stock) : null,\n            affects_stock: inventoryScope === \"option\" && attribute.name.trim().toLowerCase() === inventoryAttributeName,\n          };\n        })\n        .filter(Boolean) as Array<{\n        option_id: string | null;\n        attribute_name: string;\n        label: string;\n        price: number;\n        price_applies_per_quantity: boolean;\n        stock: number | null;\n        affects_stock: boolean;\n      }>;\n    });\n  }, [effectiveSelectedOptions, inventoryAttributeName, inventoryScope, orderedAttributes]);\n\n  const trackedOptionStocks = selectedOptionEntries\n    .filter((entry) => entry.affects_stock && typeof entry.stock === \"number\")\n    .map((entry) => Number(entry.stock));\n  const matchedVariant = useMemo(() => {\n    if (inventoryScope !== \"variant\") return null;\n    const selectedFlat: Record<string, string> = {};\n    for (const [attribute, labels] of Object.entries(effectiveSelectedOptions)) {\n      const label = labels[0];\n      if (!label) continue;\n      selectedFlat[attribute.trim().toLowerCase()] = label.trim().toLowerCase();\n    }\n    const selectedKeys = Object.keys(selectedFlat).sort();\n    if (!selectedKeys.length) return null;\n    const variations = Array.isArray(product.product_variations) ? product.product_variations : [];\n    for (const variation of variations) {\n      const comboRaw = variation.combination || {};\n      const combo: Record<string, string> = {};\n      for (const [attribute, label] of Object.entries(comboRaw)) {\n        combo[attribute.trim().toLowerCase()] = (label || \"\").trim().toLowerCase();\n      }\n      const comboKeys = Object.keys(combo).sort();\n      if (comboKeys.length !== selectedKeys.length) continue;\n      const same = selectedKeys.every((key) => combo[key] === selectedFlat[key]);\n      if (same) return variation;\n    }\n    return null;\n  }, [effectiveSelectedOptions, inventoryScope, product.product_variations]);\n  const selectedVariationSignature = useMemo(() => {\n    if (matchedVariant?.combination) {\n      return normalizeVariationSignature(matchedVariant.combination);\n    }\n    const selectedFlat: Record<string, string> = {};\n    for (const [attribute, labels] of Object.entries(effectiveSelectedOptions)) {\n      const label = labels[0];\n      if (!label) continue;\n      selectedFlat[attribute] = label;\n    }\n    return normalizeVariationSignature(selectedFlat);\n  }, [effectiveSelectedOptions, matchedVariant]);\n  const alwaysVisibleCustomFields = useMemo(\n    () => customFieldsConfig.filter((field) => !field.variation_signature),\n    [customFieldsConfig]\n  );\n  const linkedVisibleCustomFields = useMemo(() => {\n    if (!customFieldsConfig.length) return [] as NormalizedCustomField[];\n    return customFieldsConfig.filter((field) => {\n      if (!field.variation_signature) return false;\n      return matchesFieldVariationSignature(field.variation_signature, {\n        selectedVariationSignature,\n        selectedOptions: effectiveSelectedOptions,\n      });\n    });\n  }, [customFieldsConfig, effectiveSelectedOptions, selectedVariationSignature]);\n  const visibleCustomFields = useMemo(\n    () => [...alwaysVisibleCustomFields, ...linkedVisibleCustomFields].sort((a, b) => a.position - b.position),\n    [alwaysVisibleCustomFields, linkedVisibleCustomFields]\n  );\n  const missingRequiredCustomFields = useMemo(() => {\n    return visibleCustomFields.filter((field) => {\n      if (!field.required) return false;\n      const value = String(customFieldValues[field.id] || \"\").trim();\n      return !value;\n    });\n  }, [customFieldValues, visibleCustomFields]);\n  const hasValidCustomFields = missingRequiredCustomFields.length === 0;\n  const hasTrackedSelection =\n    !trackInventory\n      ? true\n      : inventoryScope === \"option\"\n        ? selectedOptionEntries.some((entry) => entry.affects_stock)\n        : inventoryScope === \"variant\"\n          ? matchedVariant !== null\n          : true;\n\n  const currentStock =\n    !trackInventory\n      ? Number.POSITIVE_INFINITY\n      : inventoryScope === \"option\" && trackedOptionStocks.length > 0\n        ? Math.min(...trackedOptionStocks)\n        : inventoryScope === \"variant\" && matchedVariant\n          ? Number(matchedVariant.stock) || 0\n          : product.stock;\n  const normalizedStock = Number.isFinite(Number(currentStock))\n    ? Math.max(0, Number(currentStock))\n    : Number.POSITIVE_INFINITY;\n  \n  const isVariationSelected = hasVariations\n    ? orderedAttributes.every((attr) => {\n        if (!attr.is_required) return true;\n        return (effectiveSelectedOptions[attr.name] || []).length > 0;\n      })\n    : true;\n\n  const handleOptionSelect = (attrName: string, option: string, allowMultiple?: boolean, isRequiredOption?: boolean) => {\n    setAddError(\"\");\n    setSelectedOptions((prev) => {\n      const required = requiredOptionsByAttr[attrName] || [];\n      const current = allowMultiple\n        ? Array.from(new Set([...required, ...(prev[attrName] || [])]))\n        : (prev[attrName]?.[0] ? [prev[attrName][0]] : (required[0] ? [required[0]] : []));\n      if (allowMultiple) {\n        const exists = current.includes(option);\n        if (exists) {\n          if (isRequiredOption) return prev;\n          const next = (prev[attrName] || []).filter((v) => v !== option);\n          return { ...prev, [attrName]: next };\n        }\n        return { ...prev, [attrName]: [...(prev[attrName] || []), option] };\n      }\n      const isCurrentlySelected = current[0] === option;\n      if (isCurrentlySelected) {\n        if (isRequiredOption) return prev;\n        const next = { ...prev };\n        delete next[attrName];\n        return next;\n      }\n      return { ...prev, [attrName]: [option] };\n    });\n  };\n\n  const unitType = product.unit_type || \"unit\";\n  const isSquareMeter = unitType === \"square_meter\";\n  const usesAllowedQuantitySteps = !isSquareMeter && allowedQuantities.length > 0;\n  const isIntervalMode = Boolean(product.allowed_sale_qty_enabled) && allowedQuantities.length > 0;\n  const selectedQuantityRaw =\n    usesAllowedQuantitySteps && !allowedQuantities.some((value) => Math.abs(value - quantity) <= 0.000001)\n      ? allowedQuantities[0]\n      : quantity;\n  const selectedQuantity = Math.max(minimumQuantity, selectedQuantityRaw);\n  const quantityInputDigits = quantityInput.replace(/\\D/g, \"\");\n  const manualInputQuantity =\n    !usesAllowedQuantitySteps && quantityInputDigits\n      ? Math.max(minimumQuantity, Number.parseInt(quantityInputDigits, 10))\n      : null;\n  const quantityForValidation =\n    usesAllowedQuantitySteps || manualInputQuantity === null ? selectedQuantity : manualInputQuantity;\n\n  const formatDigitsToMeters = (digits: string) => {\n    const cleaned = digits.replace(/\\D/g, \"\");\n    if (!cleaned) return \"\";\n\n    const padded = cleaned.padStart(3, \"0\");\n    const intPartRaw = padded.slice(0, -2);\n    const intPart = intPartRaw.replace(/^0+(?=\\d)/, \"\") || \"0\";\n    const fracPart = padded.slice(-2);\n    return `${intPart},${fracPart}`;\n  };\n\n  const widthM = formatDigitsToMeters(widthDigits);\n  const heightM = formatDigitsToMeters(heightDigits);\n  const parsedWidth = Number(widthM.replace(\",\", \".\"));\n  const parsedHeight = Number(heightM.replace(\",\", \".\"));\n  const isValidDims = isSquareMeter ? parsedWidth > 0 && parsedHeight > 0 : true;\n\n  const getQuantityCheck = (nextQuantity: number) => {\n    const effectiveQuantity = calculateEffectiveQuantity({\n      quantity: nextQuantity,\n      unit_type: unitType,\n      width_m: isSquareMeter ? parsedWidth : undefined,\n      height_m: isSquareMeter ? parsedHeight : undefined,\n    });\n\n    return checkSaleQuantityLimits({\n      min_sale_qty_enabled: product.min_sale_qty_enabled,\n      min_sale_qty: product.min_sale_qty,\n      max_sale_qty_enabled: product.max_sale_qty_enabled,\n      max_sale_qty: product.max_sale_qty,\n      allowed_sale_qty_enabled: product.allowed_sale_qty_enabled,\n      allowed_sale_quantities: product.allowed_sale_quantities,\n      effectiveQuantity,\n    });\n  };\n  const hasStockForQuantity = (nextQuantity: number) => {\n    if (!trackInventory) return true;\n    if (!Number.isFinite(normalizedStock)) return true;\n    const nextEffectiveQuantity = calculateEffectiveQuantity({\n      quantity: nextQuantity,\n      unit_type: unitType,\n      width_m: isSquareMeter ? parsedWidth : undefined,\n      height_m: isSquareMeter ? parsedHeight : undefined,\n    });\n    return nextEffectiveQuantity <= normalizedStock + 0.000001;\n  };\n\n  const quantityCheck = getQuantityCheck(quantityForValidation);\n\n  const nextQuantityDown = usesAllowedQuantitySteps\n    ? resolveAdjacentAllowedQuantity(allowedQuantities, selectedQuantity, -1)\n    : Math.max(minimumQuantity, selectedQuantity - 1);\n  const nextQuantityUp = usesAllowedQuantitySteps\n    ? resolveAdjacentAllowedQuantity(allowedQuantities, selectedQuantity, 1)\n    : selectedQuantity + 1;\n  const canDecreaseQuantity =\n    nextQuantityDown !== null &&\n    nextQuantityDown > 0 &&\n    (usesAllowedQuantitySteps ? getQuantityCheck(nextQuantityDown).isValid : selectedQuantity > minimumQuantity);\n  const canIncreaseQuantity =\n    nextQuantityUp !== null &&\n    getQuantityCheck(nextQuantityUp).isValid &&\n    hasStockForQuantity(nextQuantityUp);\n\n  const handleQuantityChange = (delta: number) => {\n    setAddError(\"\");\n    if (usesAllowedQuantitySteps) {\n      const direction: -1 | 1 = delta < 0 ? -1 : 1;\n      const nextAllowed = resolveAdjacentAllowedQuantity(allowedQuantities, selectedQuantity, direction);\n      if (nextAllowed === null) return;\n      if (!getQuantityCheck(nextAllowed).isValid) return;\n      if (!hasStockForQuantity(nextAllowed)) return;\n      setQuantity(nextAllowed);\n      setQuantityInput(formatQuantityValue(nextAllowed));\n      return;\n    }\n\n    const next = Math.max(minimumQuantity, selectedQuantity + delta);\n    if (!getQuantityCheck(next).isValid) return;\n    if (!hasStockForQuantity(next)) return;\n    setQuantity(next);\n    setQuantityInput(String(next));\n  };\n\n  const handleQuantityInputChange = (rawValue: string) => {\n    if (usesAllowedQuantitySteps) return;\n    const digitsOnly = rawValue.replace(/\\D/g, \"\");\n    setAddError(\"\");\n    setQuantityInput(digitsOnly);\n    if (!digitsOnly) return;\n    const next = Number.parseInt(digitsOnly, 10);\n    if (Number.isFinite(next) && getQuantityCheck(next).isValid && hasStockForQuantity(next)) {\n      setQuantity(Math.max(minimumQuantity, next));\n    }\n  };\n\n  const normalizeQuantityInput = () => {\n    if (usesAllowedQuantitySteps) {\n      setQuantityInput(formatQuantityValue(selectedQuantity));\n      return;\n    }\n\n    const digitsOnly = quantityInput.replace(/\\D/g, \"\");\n    if (!digitsOnly) {\n      setQuantityInput(String(selectedQuantity));\n      return;\n    }\n    const normalized = Math.max(minimumQuantity, Number.parseInt(digitsOnly, 10));\n    if (!getQuantityCheck(normalized).isValid || !hasStockForQuantity(normalized)) {\n      setQuantityInput(String(normalized));\n      return;\n    }\n    setQuantity(normalized);\n    setQuantityInput(String(normalized));\n  };\n\n  const areaEach = isSquareMeter && isValidDims ? parsedWidth * parsedHeight : 0;\n  const totalArea = isSquareMeter && isValidDims ? areaEach * quantityForValidation : 0;\n  const effectiveQty = calculateEffectiveQuantity({\n    quantity: quantityForValidation,\n    unit_type: unitType,\n    width_m: isSquareMeter ? parsedWidth : undefined,\n    height_m: isSquareMeter ? parsedHeight : undefined,\n  });\n  const exceedsStock = trackInventory && Number.isFinite(normalizedStock) && effectiveQty > normalizedStock + 0.000001;\n  const safeEffectiveQty = effectiveQty > 0 ? effectiveQty : 1;\n  const optionsPrice = optionPriceBreakdown.perQuantity + optionPriceBreakdown.oneTime / safeEffectiveQty;\n  const currentPrice = product.use_variation_price_only ? optionsPrice : product.price + optionsPrice;\n\n  const intervalTierUnitPrices = useMemo(() => {\n    if (!isIntervalMode) return [] as number[];\n    return (product.product_price_tiers || [])\n      .filter((tier) => tier.active !== false)\n      .map((tier) => {\n        const minQty = Number(tier.min_qty);\n        const maxQty = tier.max_qty === null || tier.max_qty === undefined ? null : Number(tier.max_qty);\n        const unitPrice = Number(tier.unit_price);\n        if (!Number.isFinite(minQty) || minQty <= 0 || !Number.isFinite(unitPrice) || unitPrice <= 0) return null;\n        if (maxQty === null || !Number.isFinite(maxQty) || Math.abs(maxQty - minQty) > 0.000001) return null;\n        const isAllowed = allowedQuantities.some((value) => Math.abs(value - minQty) <= 0.000001);\n        if (!isAllowed) return null;\n        return unitPrice;\n      })\n      .filter((value): value is number => value !== null);\n  }, [allowedQuantities, isIntervalMode, product.product_price_tiers]);\n\n  const pricingBase = Number(product.price) || 0;\n  const tierResolution = resolveTierPrice(pricingBase, effectiveQty, product.product_price_tiers || []);\n  const resolvedUnitPrice = product.use_variation_price_only\n    ? Math.max(0, optionsPrice)\n    : Math.max(0, tierResolution.unitPrice + optionsPrice);\n  const appliedTier = tierResolution.tier;\n  const appliedDiscountPct = product.use_variation_price_only\n    ? 0\n    : calculateTierDiscountPct(pricingBase, tierResolution.unitPrice);\n  const showcaseTierDiscount = getBestTierDiscount(pricingBase, product.product_price_tiers || []);\n\n  const baseTotalPrice = isSquareMeter\n    ? (isValidDims ? resolvedUnitPrice * effectiveQty : 0)\n    : resolvedUnitPrice * effectiveQty;\n  const totalPrice = product.price_on_request\n    ? null\n    : baseTotalPrice;\n  const combosTotalPrice = useMemo(() => {\n    if (!selectedCombos || selectedCombos.length === 0) return 0;\n    return selectedCombos.reduce((sum, selectedCombo) => {\n      const unitPrice = Number(selectedCombo?.discountedPrice) || 0;\n      const quantity = Number(selectedCombo?.quantity) || 0;\n      return sum + unitPrice * quantity;\n    }, 0);\n  }, [selectedCombos]);\n  const totalWithCombos = totalPrice === null ? null : totalPrice + combosTotalPrice;\n  const hasSelectedCombos = selectedCombos.length > 0;\n\n  const lowestIntervalUnitPrice = intervalTierUnitPrices.length > 0 ? Math.min(...intervalTierUnitPrices) : null;\n  const formatMoney = (value: number) =>\n    new Intl.NumberFormat(\"pt-BR\", { style: \"currency\", currency: \"BRL\" }).format(value);\n  const priceLabel = product.price_on_request\n    ? \"Sob consulta\"\n    : product.use_variation_price_only && optionsPrice <= 0 && minimumVariationUnitPrice > 0\n      ? formatMoney(minimumVariationUnitPrice)\n    : isIntervalMode && lowestIntervalUnitPrice !== null\n      ? formatMoney(lowestIntervalUnitPrice)\n      : isSquareMeter\n        ? `${formatMoney(resolvedUnitPrice)} / m²`\n        : formatMoney(resolvedUnitPrice);\n  const hideSquareMeterUnitPrice = isSquareMeter && !product.price_on_request;\n  const priceTitle = product.use_variation_price_only && optionsPrice <= 0 && minimumVariationUnitPrice > 0\n    ? \"A partir de\"\n    : isIntervalMode\n    ? \"A partir de\"\n    : hideSquareMeterUnitPrice\n      ? \"Preço sob medida\"\n      : isSquareMeter\n        ? \"Preço por m²\"\n        : \"Preço unitário\";\n  const priceValue = hideSquareMeterUnitPrice\n    ? (isValidDims ? formatMoney(totalPrice || 0) : \"Informe as medidas\")\n    : priceLabel;\n\n  const quantityRuleMessage = useMemo(() => {\n    if (quantityCheck.isBelowMin && quantityCheck.minQty !== null) {\n      return `Quantidade minima: ${formatQuantityValue(quantityCheck.minQty)}.`;\n    }\n    if (quantityCheck.isAboveMax && quantityCheck.maxQty !== null) {\n      return `Quantidade maxima: ${formatQuantityValue(quantityCheck.maxQty)}.`;\n    }\n    if (quantityCheck.isNotAllowedValue && quantityCheck.allowedQuantities.length > 0) {\n      return `Use quantidade permitida: ${quantityCheck.allowedQuantities\n        .map((value) => formatQuantityValue(value))\n        .join(\", \")}.`;\n    }\n    return \"\";\n  }, [\n    quantityCheck.allowedQuantities,\n    quantityCheck.isAboveMax,\n    quantityCheck.isBelowMin,\n    quantityCheck.isNotAllowedValue,\n    quantityCheck.maxQty,\n    quantityCheck.minQty,\n  ]);\n  const stockMaxHelperMessage = useMemo(() => {\n    if (!trackInventory) return \"\";\n    if (inventoryScope !== \"product\" && !hasTrackedSelection) {\n      return \"\";\n    }\n    if (!Number.isFinite(normalizedStock)) return \"\";\n    return `Máximo disponível: ${formatQuantityValue(normalizedStock)}.`;\n  }, [hasTrackedSelection, inventoryScope, normalizedStock, trackInventory]);\n\n  const productForCart = (() => {\n    return {\n      ...product,\n      stock: normalizedStock,\n      track_inventory: trackInventory,\n    };\n  })();\n\n  const whatsappDigits = normalizeWhatsappForLink(whatsappNumber);\n  const trustIconMap = useMemo(() => ({\n    truck: Truck,\n    shieldCheck: ShieldCheck,\n    creditCard: CreditCard,\n    package: Package,\n    clock: Clock,\n    badgeCheck: BadgeCheck,\n    mapPin: MapPin,\n    headphones: Headphones,\n  }), []);\n  const normalizedTrustItems = useMemo(() => {\n    const source = Array.isArray(trustItems) ? trustItems : [];\n    const items = source\n      .map((item) => ({\n        icon: String(item?.icon || \"\").trim(),\n        title: String(item?.title || \"\").trim(),\n      }))\n      .filter((item) => item.icon || item.title)\n      .slice(0, 2);\n    if (items.length >= 2) return items;\n    const defaults = [\n      { icon: \"truck\", title: \"Entrega Rápida e Segura\" },\n      { icon: \"shieldCheck\", title: \"Garantia de Qualidade\" },\n    ];\n    while (items.length < 2) items.push(defaults[items.length]);\n    return items;\n  }, [trustItems]);\n  const primaryTextColor = useMemo(() => getReadableTextColor(primaryColor), [primaryColor]);\n  const primaryOnWhite = useMemo(\n    () => ensureColorContrast(primaryColor, \"#FFFFFF\", { minRatio: 4.5 }),\n    [primaryColor]\n  );\n  const quantityInputValue = usesAllowedQuantitySteps ? formatQuantityValue(selectedQuantity) : quantityInput;\n  const activePriceTiers = (product.product_price_tiers || []).filter((tier) => {\n    if (tier.active === false) return false;\n    const minQty = Number(tier.min_qty);\n    const unitPrice = Number(tier.unit_price);\n    return Number.isFinite(minQty) && minQty > 0 && Number.isFinite(unitPrice) && unitPrice > 0;\n  });\n  const quantityPricingRows = (() => {\n    if (isSquareMeter || product.price_on_request || activePriceTiers.length === 0) return [] as Array<{\n      quantity: number;\n      unitPrice: number;\n      totalPrice: number;\n      isSelected: boolean;\n      disabled: boolean;\n    }>;\n    const candidateQuantities = isIntervalMode && allowedQuantities.length > 0\n      ? allowedQuantities\n      : Array.from(\n          new Set(\n            [minimumQuantity, ...activePriceTiers.map((tier) => Number(tier.min_qty))]\n              .filter((value) => Number.isFinite(value) && value > 0)\n              .map((value) => Number(value.toFixed(3)))\n          )\n        ).sort((a, b) => a - b);\n    return candidateQuantities.map((qty) => {\n      const effectiveQuantity = calculateEffectiveQuantity({\n        quantity: qty,\n        unit_type: unitType,\n      });\n      const safeRowEffectiveQty = effectiveQuantity > 0 ? effectiveQuantity : 1;\n      const rowOptionUnitAdjustment =\n        optionPriceBreakdown.perQuantity + optionPriceBreakdown.oneTime / safeRowEffectiveQty;\n      const rowTierResolution = resolveTierPrice(pricingBase, effectiveQuantity, product.product_price_tiers || []);\n      const baseUnitPrice =\n        product.use_variation_price_only\n          ? Math.max(0, rowOptionUnitAdjustment)\n          : Math.max(0, rowTierResolution.unitPrice + rowOptionUnitAdjustment);\n      const unitPrice = baseUnitPrice;\n      const totalPrice = unitPrice * effectiveQuantity;\n      const isValid = getQuantityCheck(qty).isValid && hasStockForQuantity(qty);\n      return {\n        quantity: qty,\n        unitPrice,\n        totalPrice,\n        isSelected: Math.abs(selectedQuantity - qty) <= 0.000001,\n        disabled: !isValid,\n      };\n    });\n  })();\n\n  const handleQuantityFromPricingTable = (nextQuantity: number) => {\n    if (!Number.isFinite(nextQuantity) || nextQuantity <= 0) return;\n    if (!getQuantityCheck(nextQuantity).isValid || !hasStockForQuantity(nextQuantity)) return;\n    setAddError(\"\");\n    setQuantity(nextQuantity);\n    setQuantityInput(formatQuantityValue(nextQuantity));\n  };\n\n  const customFieldInputClass =\n    \"w-full rounded-xl border bg-white px-3 py-2.5 text-sm text-slate-800 placeholder:text-[var(--cf-placeholder)] transition-colors focus:outline-none\";\n  const customFieldTextColor = ensureColorContrast(primaryColor, \"#FFFFFF\", { minRatio: 4.5 });\n  const customFieldPlaceholderColor = colorWithAlpha(customFieldTextColor, 0.62);\n  const customFieldBorder = colorWithAlpha(primaryColor, 0.55);\n  const customFieldFocusRing = colorWithAlpha(primaryColor, 0.28);\n  const linkedFieldBorder = colorWithAlpha(primaryColor, 0.55);\n  const linkedFieldBg = colorWithAlpha(primaryColor, 0.1);\n  const numberBadgeBorder = colorWithAlpha(primaryColor, 0.6);\n  const numberBadgeBg = colorWithAlpha(primaryColor, 0.16);\n  const qtyTableBorder = colorWithAlpha(primaryColor, 0.34);\n  const qtyTableHeaderBg = colorWithAlpha(primaryColor, 0.11);\n  const qtyTableHeaderText = ensureColorContrast(primaryColor, \"#FFFFFF\", { minRatio: 4.5 });\n  const qtyTableRowText = ensureColorContrast(primaryColor, \"#FFFFFF\", { minRatio: 7 });\n  const qtyTableRowHoverBg = colorWithAlpha(primaryColor, 0.06);\n  const qtyTableRowSelectedBg = colorWithAlpha(primaryColor, 0.13);\n  const qtyTableRowDisabledText = colorWithAlpha(qtyTableRowText, 0.55);\n\n  const handleCustomFieldValueChange = (fieldId: string, rawValue: string, isNumberField: boolean) => {\n    setAddError(\"\");\n    if (!isNumberField) {\n      setCustomFieldValues((prev) => ({ ...prev, [fieldId]: rawValue }));\n      return;\n    }\n    const normalized = rawValue\n      .replace(/[^\\d,.-]/g, \"\")\n      .replace(/(,.*),/g, \"$1\")\n      .replace(/(\\..*)\\./g, \"$1\");\n    setCustomFieldValues((prev) => ({ ...prev, [fieldId]: normalized }));\n  };\n\n  const renderCustomField = (field: NormalizedCustomField, linkedToVariation: boolean) => (\n    <div\n      key={field.id}\n      className={\n        linkedToVariation\n          ? \"space-y-1 rounded-xl border p-3\"\n          : \"space-y-1\"\n      }\n      style={linkedToVariation ? { borderColor: linkedFieldBorder, backgroundColor: linkedFieldBg } : undefined}\n    >\n      <label\n        className=\"text-sm font-medium\"\n        style={{ color: customFieldTextColor }}\n      >\n        {field.title}\n        {field.required ? <span className=\"text-rose-600\"> *</span> : null}\n      </label>\n      {field.field_type === \"textarea\" ? (\n        <textarea\n          value={customFieldValues[field.id] || \"\"}\n          onChange={(e) => handleCustomFieldValueChange(field.id, e.target.value, false)}\n          placeholder={field.placeholder || \"\"}\n          rows={3}\n          className={customFieldInputClass}\n          style={{\n            borderColor: customFieldBorder,\n            boxShadow: `0 0 0 0 ${customFieldFocusRing}`,\n            color: customFieldTextColor,\n            caretColor: primaryColor,\n            [\"--cf-placeholder\" as string]: customFieldPlaceholderColor,\n          }}\n          onFocus={(e) => {\n            e.currentTarget.style.borderColor = primaryColor;\n            e.currentTarget.style.boxShadow = `0 0 0 3px ${customFieldFocusRing}`;\n          }}\n          onBlur={(e) => {\n            e.currentTarget.style.borderColor = customFieldBorder;\n            e.currentTarget.style.boxShadow = \"0 0 0 0 transparent\";\n          }}\n        />\n      ) : field.field_type === \"number\" ? (\n        <div className=\"relative\">\n          <span\n            className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 inline-flex h-6 min-w-6 items-center justify-center rounded-md border px-1 text-[11px] font-semibold\"\n            style={{ borderColor: numberBadgeBorder, backgroundColor: numberBadgeBg, color: customFieldTextColor }}\n          >\n            <Hash size={12} />\n          </span>\n          <input\n            type=\"text\"\n            inputMode=\"decimal\"\n            value={customFieldValues[field.id] || \"\"}\n            onChange={(e) => handleCustomFieldValueChange(field.id, e.target.value, true)}\n            placeholder={field.placeholder || \"Digite um numero\"}\n            className={`${customFieldInputClass} pl-11`}\n            style={{\n              borderColor: customFieldBorder,\n              boxShadow: `0 0 0 0 ${customFieldFocusRing}`,\n              color: customFieldTextColor,\n              caretColor: primaryColor,\n              [\"--cf-placeholder\" as string]: customFieldPlaceholderColor,\n            }}\n            onFocus={(e) => {\n              e.currentTarget.style.borderColor = primaryColor;\n              e.currentTarget.style.boxShadow = `0 0 0 3px ${customFieldFocusRing}`;\n            }}\n            onBlur={(e) => {\n              e.currentTarget.style.borderColor = customFieldBorder;\n              e.currentTarget.style.boxShadow = \"0 0 0 0 transparent\";\n            }}\n          />\n        </div>\n      ) : (\n        <input\n          type=\"text\"\n          value={customFieldValues[field.id] || \"\"}\n          onChange={(e) => handleCustomFieldValueChange(field.id, e.target.value, false)}\n          placeholder={field.placeholder || \"\"}\n          className={customFieldInputClass}\n          style={{\n            borderColor: customFieldBorder,\n            boxShadow: `0 0 0 0 ${customFieldFocusRing}`,\n            color: customFieldTextColor,\n            caretColor: primaryColor,\n            [\"--cf-placeholder\" as string]: customFieldPlaceholderColor,\n          }}\n          onFocus={(e) => {\n            e.currentTarget.style.borderColor = primaryColor;\n            e.currentTarget.style.boxShadow = `0 0 0 3px ${customFieldFocusRing}`;\n          }}\n          onBlur={(e) => {\n            e.currentTarget.style.borderColor = customFieldBorder;\n            e.currentTarget.style.boxShadow = \"0 0 0 0 transparent\";\n          }}\n        />\n      )}\n    </div>\n  );\n\n  return (\n    <div className=\"flex flex-col gap-6\">\n      {/* Price Display */}\n      <div className=\"flex items-center justify-between p-4 bg-gray-50 rounded-lg border border-gray-100\">\n         <span className=\"text-gray-500 font-medium\">\n           {priceTitle}\n         </span>\n         <span className={`font-bold ${hideSquareMeterUnitPrice ? \"text-xl text-violet-700\" : \"text-2xl text-gray-900\"}`}>{priceValue}</span>\n      </div>\n      {showcaseTierDiscount && !product.price_on_request && !isIntervalMode && (\n        <div className=\"-mt-2\">\n          <span className=\"inline-flex items-center rounded-full border border-emerald-200 bg-emerald-50 px-3 py-1 text-[11px] font-bold uppercase tracking-wide text-emerald-700\">\n            {formatDiscountPct(showcaseTierDiscount.discountPct)}% OFF a partir de{\" \"}\n            {formatTierThreshold(showcaseTierDiscount.tier.min_qty, unitType)}\n          </span>\n        </div>\n      )}\n      {appliedTier && !product.price_on_request && !isIntervalMode && (\n        <div className={`text-xs -mt-3 ${appliedDiscountPct > 0 ? \"text-emerald-600\" : \"text-sky-600\"}`}>\n          {appliedDiscountPct > 0\n            ? `Desconto aplicado: ${formatDiscountPct(appliedDiscountPct)}% OFF (a partir de ${formatTierThreshold(appliedTier.min_qty, unitType)}).`\n            : `Faixa de preço aplicada a partir de ${formatTierThreshold(appliedTier.min_qty, unitType)}.`}\n        </div>\n      )}\n\n      {hasVariations && (\n        <div className=\"space-y-4\">\n          {orderedAttributes.map((attr) => (\n            <div key={attr.id} className=\"space-y-2\">\n              <div className=\"flex flex-wrap items-center gap-2\">\n                <span className=\"text-xs font-bold text-gray-600 uppercase tracking-wide\">\n                  {attr.name}\n                </span>\n                {attr.allow_multiple && (\n                  <span className=\"text-[10px] px-2 py-0.5 rounded-full border border-gray-200 text-gray-500 uppercase tracking-wide\">\n                    Múltipla escolha\n                  </span>\n                )}\n                {attr.is_required && (\n                  <span\n                    className=\"text-[10px] px-2 py-0.5 rounded-full border uppercase tracking-wide\"\n                    style={{ borderColor: primaryOnWhite, color: primaryOnWhite }}\n                  >\n                    Obrigatório\n                  </span>\n                )}\n              </div>\n              <div className=\"flex flex-wrap gap-2\">\n                {attr.options && Array.isArray(attr.options) && [...attr.options]\n                  .map((optionOrString, index) => {\n                    if (typeof optionOrString === \"string\") {\n                      return { label: optionOrString, price: 0, is_required: false, position: index };\n                    }\n                    return { ...optionOrString, position: optionOrString.position ?? index };\n                  })\n                  .sort((a, b) => (a.position ?? 0) - (b.position ?? 0))\n                  .map((optionOrString) => {\n                  const optionLabel = optionOrString.label;\n                  const optionPrice = optionOrString.price || 0;\n                  const isSelected = (effectiveSelectedOptions[attr.name] || []).includes(optionLabel);\n                  const isRequiredOption = Boolean(optionOrString.is_required);\n                  \n                  return (\n                    <button\n                      key={optionLabel}\n                      onClick={() => handleOptionSelect(attr.name, optionLabel, Boolean(attr.allow_multiple), isRequiredOption)}\n                      className={`px-4 py-2 rounded-lg text-sm font-medium border transition-all flex items-center gap-2 ${\n                        isSelected\n                          ? \"shadow-md\"\n                          : \"bg-white text-gray-700 border-gray-200 hover:border-gray-300 hover:bg-gray-50\"\n                      }`}\n                      style={isSelected ? { backgroundColor: primaryColor, borderColor: primaryColor, color: primaryTextColor } : undefined}\n                    >\n                      <span>{optionLabel}</span>\n                      {isRequiredOption && (\n                        <span\n                          className={`inline-flex h-4 w-4 items-center justify-center rounded-full text-[10px] font-bold ${\n                            isSelected ? \"bg-white/90 text-gray-800\" : \"\"\n                          }`}\n                          style={!isSelected ? { backgroundColor: primaryColor, color: primaryTextColor } : undefined}\n                          title=\"Item obrigatório por padrão\"\n                        >\n                          ?\n                        </span>\n                      )}\n                      {optionPrice > 0 && (\n                        <span\n                          className={`text-xs px-1.5 py-0.5 rounded ${isSelected ? \"bg-white/20\" : \"bg-gray-100 text-gray-500\"}`}\n                          style={isSelected ? { color: primaryTextColor } : undefined}\n                        >\n                          +R$ {optionPrice.toFixed(2)}\n                        </span>\n                      )}\n                    </button>\n                  );\n                })}\n              </div>\n              {linkedVisibleCustomFields\n                .filter((field) => {\n                  const signature = normalizeText(field.variation_signature || \"\");\n                  if (!signature.startsWith(\"option::\")) return false;\n                  const selected = effectiveSelectedOptions[attr.name] || [];\n                  return selected.some(\n                    (selectedOptionLabel) =>\n                      signature === normalizeText(buildOptionSignature(attr.name, selectedOptionLabel))\n                  );\n                })\n                .map((field) => (\n                  <div key={field.id} className=\"pt-1\">\n                    {renderCustomField(field, true)}\n                  </div>\n                ))}\n            </div>\n          ))}\n        </div>\n      )}\n\n      {linkedVisibleCustomFields\n        .filter((field) => !normalizeText(field.variation_signature || \"\").startsWith(\"option::\"))\n        .map((field) => renderCustomField(field, true))}\n\n      {alwaysVisibleCustomFields.length > 0 && (\n        <div className=\"space-y-3\">\n          {alwaysVisibleCustomFields.map((field) => (\n            renderCustomField(field, false)\n          ))}\n        </div>\n      )}\n\n      {isSquareMeter && (\n        <div className=\"grid grid-cols-2 gap-4\">\n          <div className=\"space-y-1\">\n            <span className=\"text-xs font-bold text-gray-600 uppercase tracking-wide\">Largura (m)</span>\n            <input\n              inputMode=\"numeric\"\n              value={widthM}\n              onKeyDown={(e) => {\n                if (e.key !== \"Backspace\") return;\n                const hasSelection = e.currentTarget.selectionStart !== e.currentTarget.selectionEnd;\n                if (hasSelection) return;\n                e.preventDefault();\n                setWidthDigits((d) => d.slice(0, -1));\n              }}\n              onChange={(e) => {\n                setAddError(\"\");\n                setWidthDigits(e.target.value.replace(/\\D/g, \"\"));\n              }}\n              className=\"w-full px-3 py-2 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-[var(--ks-color-primary)]/30 focus:border-[var(--ks-color-primary)]\"\n              placeholder=\"Ex: 1,20\"\n            />\n          </div>\n          <div className=\"space-y-1\">\n            <span className=\"text-xs font-bold text-gray-600 uppercase tracking-wide\">Altura (m)</span>\n            <input\n              inputMode=\"numeric\"\n              value={heightM}\n              onKeyDown={(e) => {\n                if (e.key !== \"Backspace\") return;\n                const hasSelection = e.currentTarget.selectionStart !== e.currentTarget.selectionEnd;\n                if (hasSelection) return;\n                e.preventDefault();\n                setHeightDigits((d) => d.slice(0, -1));\n              }}\n              onChange={(e) => {\n                setAddError(\"\");\n                setHeightDigits(e.target.value.replace(/\\D/g, \"\"));\n              }}\n              className=\"w-full px-3 py-2 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-[var(--ks-color-primary)]/30 focus:border-[var(--ks-color-primary)]\"\n              placeholder=\"Ex: 0,80\"\n            />\n          </div>\n        </div>\n      )}\n\n      {quantityPricingRows.length > 0 && (\n        <div className=\"space-y-2\">\n          <span className=\"text-sm font-bold\" style={{ color: qtyTableHeaderText }}>Escolha a quantidade</span>\n          <div\n            className=\"max-h-72 overflow-auto rounded-xl border bg-white md:max-h-none\"\n            style={{ borderColor: qtyTableBorder }}\n          >\n            <div\n              className=\"sticky top-0 z-10 grid grid-cols-[1.2fr_1fr_1fr] border-b backdrop-blur\"\n              style={{ borderColor: qtyTableBorder, backgroundColor: qtyTableHeaderBg }}\n            >\n              <span className=\"px-3 py-2 text-xs font-bold uppercase tracking-wide\" style={{ color: qtyTableHeaderText }}>Quantidade</span>\n              <span className=\"px-3 py-2 text-xs font-bold uppercase tracking-wide\" style={{ color: qtyTableHeaderText }}>Valor por unidade</span>\n              <span className=\"px-3 py-2 text-xs font-bold uppercase tracking-wide\" style={{ color: qtyTableHeaderText }}>Valor total</span>\n            </div>\n            {quantityPricingRows.map((row) => (\n              <label\n                key={`qty-tier-${row.quantity}`}\n                className={`grid grid-cols-[1.2fr_1fr_1fr] items-center border-b last:border-b-0 ${\n                  row.disabled ? \"cursor-not-allowed\" : \"cursor-pointer\"\n                }`}\n                style={{\n                  borderColor: qtyTableBorder,\n                  color: row.disabled ? qtyTableRowDisabledText : qtyTableRowText,\n                  backgroundColor: row.isSelected ? qtyTableRowSelectedBg : \"transparent\",\n                }}\n                onMouseEnter={(e) => {\n                  if (!row.disabled && !row.isSelected) e.currentTarget.style.backgroundColor = qtyTableRowHoverBg;\n                }}\n                onMouseLeave={(e) => {\n                  if (!row.disabled) e.currentTarget.style.backgroundColor = row.isSelected ? qtyTableRowSelectedBg : \"transparent\";\n                }}\n              >\n                <span className=\"flex items-center gap-2 px-3 py-3\">\n                  <input\n                    type=\"radio\"\n                    name={`qty-tier-${product.id}`}\n                    checked={row.isSelected}\n                    onChange={() => handleQuantityFromPricingTable(row.quantity)}\n                    disabled={row.disabled}\n                    className=\"h-4 w-4\"\n                    style={{ accentColor: primaryColor }}\n                  />\n                  <span className=\"text-sm font-semibold\">{formatTierThreshold(row.quantity, unitType)}</span>\n                </span>\n                <span className=\"px-3 py-3 text-sm font-medium\">{formatMoney(row.unitPrice)}</span>\n                <span className=\"px-3 py-3 text-sm font-semibold\">{formatMoney(row.totalPrice)}</span>\n              </label>\n            ))}\n          </div>\n        </div>\n      )}\n\n      {/* Quantity & Total */}\n      <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4\">\n        <div className=\"flex flex-col items-start gap-2\">\n          <div className=\"flex items-center border border-gray-200 rounded-lg bg-white\">\n            <button \n              onClick={() => handleQuantityChange(-1)}\n              disabled={!canDecreaseQuantity}\n              className=\"w-10 h-10 flex items-center justify-center text-gray-500 hover:bg-gray-50 disabled:opacity-50 transition-colors\"\n            >\n              -\n            </button>\n            <input\n              type=\"text\"\n              inputMode=\"numeric\"\n              pattern=\"[0-9]*\"\n              value={quantityInputValue}\n              onChange={(e) => handleQuantityInputChange(e.target.value)}\n              onFocus={(e) => e.currentTarget.select()}\n              onBlur={normalizeQuantityInput}\n              readOnly={usesAllowedQuantitySteps}\n              className={`w-14 h-10 text-center font-medium text-gray-900 outline-none ${\n                usesAllowedQuantitySteps ? \"cursor-not-allowed bg-gray-50 text-gray-500\" : \"\"\n              }`}\n              aria-label=\"Quantidade\"\n            />\n            <button \n              onClick={() => handleQuantityChange(1)}\n              disabled={!canIncreaseQuantity}\n              className=\"w-10 h-10 flex items-center justify-center text-gray-500 hover:bg-gray-50 transition-colors\"\n            >\n              +\n            </button>\n          </div>\n          {stockMaxHelperMessage && (\n            <span className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[11px] font-semibold ${\n              exceedsStock ? \"border-red-300 text-red-600\" : \"border-gray-300 text-gray-600\"\n            }`}>\n              {stockMaxHelperMessage}\n            </span>\n          )}\n        </div>\n        \n        <div className=\"flex flex-col items-end\">\n          <span className=\"text-xs text-gray-500 uppercase tracking-wide font-bold\">{isSquareMeter ? \"Total estimado (m²)\" : \"Total estimado\"}</span>\n          <span className=\"text-xl font-bold\" style={{ color: primaryOnWhite }}>\n            {product.price_on_request\n              ? \"Sob consulta\"\n              : formatMoney(totalPrice || 0)}\n          </span>\n          {isSquareMeter && (\n            <span className=\"text-[11px] text-gray-500\">\n              {isValidDims ? `${totalArea.toFixed(2)} m²` : \"Informe as medidas\"}\n            </span>\n          )}\n          {hasSelectedCombos && (\n            <>\n              <span className=\"text-[11px] text-gray-500\">\n                Combo selecionado: +{formatMoney(combosTotalPrice)}\n              </span>\n              <span className=\"text-sm font-bold text-emerald-600\">\n                Total com combo: {totalWithCombos === null ? \"Sob consulta\" : formatMoney(totalWithCombos)}\n              </span>\n            </>\n          )}\n          {quantityRuleMessage && (\n            <span className=\"mt-1 inline-flex items-center rounded-full border border-amber-300 px-2.5 py-1 text-[11px] font-semibold text-amber-700\">\n              {quantityRuleMessage}\n            </span>\n          )}\n        </div>\n      </div>\n\n      {combosData && combosData.length > 0 && (\n        <div className=\"pt-2\">\n          <ProductCombosWidget\n            combos={combosData}\n            mainProduct={product}\n            primaryColor={primaryColor}\n            onComboSelect={(combos) => {\n              setSelectedCombos(combos || []);\n            }}\n          />\n        </div>\n      )}\n\n      {/* Action Buttons */}\n      <div className=\"flex flex-col gap-3\">\n        <button\n          onClick={() => {\n            const customFieldsPayload = visibleCustomFields\n              .map((field) => ({\n                id: field.id,\n                title: field.title,\n                field_type: field.field_type,\n                required: field.required,\n                variation_signature: field.variation_signature,\n                value: String(customFieldValues[field.id] || \"\").trim(),\n              }))\n              .filter((field) => field.value.length > 0 || field.required);\n            if (!hasValidCustomFields) {\n              setAddError(\n                `Preencha os campos obrigatorios: ${missingRequiredCustomFields.map((field) => field.title).join(\", \")}.`\n              );\n              return;\n            }\n            const selectedOptionsDescription = Object.entries(effectiveSelectedOptions)\n              .map(([key, values]) => `${key}: ${values.join(\", \")}`)\n              .join(\" / \");\n            const customFieldsDescription = customFieldsPayload\n              .map((field) => `${field.title}: ${field.value}`)\n              .join(\" / \");\n            const finalVariationDescription = [selectedOptionsDescription, customFieldsDescription]\n              .filter(Boolean)\n              .join(\" / \");\n            const result = addToCart(\n              productForCart,\n              quantityForValidation,\n              isSquareMeter ? { width_m: parsedWidth, height_m: parsedHeight } : undefined,\n              hasVariations || customFieldsPayload.length > 0\n                ? {\n                    id: \"custom-options\",\n                    description: finalVariationDescription,\n                    selected_options: {\n                      inventory_scope: inventoryScope,\n                      inventory_attribute_name: inventoryScope === \"option\" ? inventoryAttributeName || null : null,\n                      options: selectedOptionEntries.map((entry) => ({\n                        option_id: entry.option_id,\n                        attribute_name: entry.attribute_name,\n                        label: entry.label,\n                        price: entry.price,\n                        price_applies_per_quantity: entry.price_applies_per_quantity,\n                        affects_stock: entry.affects_stock,\n                      })),\n                      custom_fields: customFieldsPayload,\n                    },\n                  }\n                : undefined\n            );\n            if (!result.ok) {\n              setAddError(result.message || \"Nao foi possivel adicionar esse produto.\");\n              return;\n            }\n\n            // Handle Combo Add\n            if (selectedCombos && selectedCombos.length > 0) {\n              let hasComboError = false;\n\n              selectedCombos.forEach((selectedCombo) => {\n                const comboItem = {\n                  id: selectedCombo.combo.combo_product.id,\n                  name: selectedCombo.combo.combo_product.name,\n                  price: selectedCombo.combo.combo_product.price,\n                  image_url: selectedCombo.combo.combo_product.image_url,\n                  user_id: product.user_id,\n                  track_inventory: false,\n                  unit_type: selectedCombo.configuration?.unit_type || \"unit\",\n                };\n\n                const comboRes = addToCart(\n                  comboItem as any, \n                  selectedCombo.quantity, \n                  { \n                   price_override: selectedCombo.discountedPrice,\n                   is_combo_item: true,\n                   combo_parent_id: result.cart_item_id, // Link combo to main product\n                   width_m: selectedCombo.configuration?.width_m,\n                   height_m: selectedCombo.configuration?.height_m,\n                  },\n                  selectedCombo.configuration?.variation\n                );\n\n                if (!comboRes.ok) {\n                  hasComboError = true;\n                }\n              });\n\n              if (hasComboError) {\n                toast.error(\"Alguns itens do combo não puderam ser adicionados.\");\n              } else {\n                toast.success(\"Produto e combo(s) adicionados!\");\n              }\n            } else {\n              toast.success(\"Adicionado ao carrinho!\");\n            }\n\n            setAddError(\"\");\n            \n            // Open Cart Drawer\n            setTimeout(() => {\n              const btn = document.querySelector('[data-ks-cart-trigger]') as HTMLButtonElement;\n              if (btn) btn.click();\n            }, 50);\n          }}\n          disabled={(trackInventory && normalizedStock <= 0) || exceedsStock || (isSquareMeter && !isValidDims) || !isVariationSelected || !quantityCheck.isValid || !hasTrackedSelection || !hasValidCustomFields}\n          className=\"w-full py-4 px-6 rounded-xl font-bold text-lg shadow-lg hover:opacity-90 transition-all transform active:scale-[0.98] flex items-center justify-center gap-3 disabled:opacity-50 disabled:cursor-not-allowed\"\n          style={{ backgroundColor: primaryColor, color: primaryTextColor }}\n        >\n          <ShoppingBag size={24} />\n          {!trackInventory || normalizedStock > 0 \n            ? (!isVariationSelected \n                ? 'Selecione as opções' \n                : (!hasTrackedSelection ? 'Selecione uma opção' : (!hasValidCustomFields ? 'Preencha os campos' : (isSquareMeter && !isValidDims ? 'Informe as medidas' : (exceedsStock ? 'Quantidade acima do estoque' : 'Adicionar ao Carrinho'))))) \n            : 'Indisponível'}\n        </button>\n        {addError && (\n          <p className=\"text-xs font-medium text-red-600\">{addError}</p>\n        )}\n\n        {whatsappDigits && (\n          <a\n            href={`https://wa.me/${whatsappDigits}?text=${encodeURIComponent(`Olá! Tenho interesse no produto ${product.name}`)}`}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"w-full py-3 px-6 rounded-xl font-bold text-gray-700 border-2 border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-all flex items-center justify-center gap-2\"\n          >\n            <MessageCircle size={20} className=\"text-[#25D366]\" />\n            Tirar dúvidas no WhatsApp\n          </a>\n        )}\n      </div>\n\n      {/* Trust Badges */}\n      <div className=\"grid grid-cols-2 gap-4 pt-4 border-t border-gray-100\">\n        {normalizedTrustItems.map((item, idx) => {\n          const Icon = trustIconMap[item.icon as keyof typeof trustIconMap] || ShieldCheck;\n          return (\n            <div key={`trust-item-${idx}`} className=\"flex items-center gap-3\">\n              <Icon className=\"text-gray-400 shrink-0\" size={20} />\n              <div className=\"flex flex-col\">\n                <span className=\"text-xs font-bold text-gray-700\">{item.title || \"Selo de confiança\"}</span>\n              </div>\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\ProductShowcase.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ProductCombosWidget' is defined but never used.","line":8,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":27,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"ProductCombosWidget"},"fix":{"range":[290,357],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":88,"column":16,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":88,"endColumn":19,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[2561,2564],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[2561,2564],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'handleAddToCartCombo' is assigned a value but never used.","line":184,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":184,"endColumn":29},{"ruleId":"@typescript-eslint/no-explicit-any","severity":2,"message":"Unexpected any. Specify a different type.","line":184,"column":52,"nodeType":"TSAnyKeyword","messageId":"unexpectedAny","endLine":184,"endColumn":55,"suggestions":[{"messageId":"suggestUnknown","fix":{"range":[6065,6068],"text":"unknown"},"desc":"Use `unknown` instead, this will force you to explicitly, and safely assert the type is correct."},{"messageId":"suggestNever","fix":{"range":[6065,6068],"text":"never"},"desc":"Use `never` instead, this is useful when instantiating generic type parameters that you don't need to know the type of."}]}],"suppressedMessages":[],"errorCount":2,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport Image from \"next/image\";\nimport { CheckCircle2, Star } from \"lucide-react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport ProductPurchaseCard from \"@/components/ProductPurchaseCard\";\nimport ProductCombosWidget from \"@/components/ProductCombosWidget\";\nimport {\n  getPrimaryProductImageId,\n  normalizeProductImages,\n  type ProductImageItem,\n} from \"@/lib/product-images\";\n\ntype ProductAttributeOption =\n  | string\n  | {\n      label: string;\n      price: number;\n      stock?: number;\n      is_required?: boolean;\n      position?: number;\n      image_id?: string | null;\n    };\n\ninterface ProductShowcaseProps {\n  product: {\n    id: string;\n    name: string;\n    description?: string | null;\n    price: number;\n    image_url?: string | null;\n    stock: number;\n    track_inventory?: boolean | null;\n    user_id: string;\n    unit_type?: string | null;\n    price_on_request?: boolean | null;\n    min_sale_qty_enabled?: boolean | null;\n    min_sale_qty?: number | null;\n    max_sale_qty_enabled?: boolean | null;\n    max_sale_qty?: number | null;\n    allowed_sale_qty_enabled?: boolean | null;\n    allowed_sale_quantities?: number[] | null;\n    product_price_tiers?: Array<{\n      min_qty: number;\n      max_qty: number | null;\n      unit_price: number;\n      sort_order?: number | null;\n      active?: boolean | null;\n    }> | null;\n    product_images?: Array<{\n      id: string;\n      image_url: string;\n      sort_order?: number | null;\n      is_primary?: boolean | null;\n    }> | null;\n    product_attributes?: Array<{\n      id: string;\n      name: string;\n      options: ProductAttributeOption[];\n      allow_multiple?: boolean | null;\n      is_required?: boolean | null;\n      position?: number | null;\n    }> | null;\n    product_variations?: Array<{\n      id: string;\n      combination: Record<string, string>;\n      price: number | null;\n      stock: number;\n      sku?: string;\n    }> | null;\n    custom_fields_config?: Array<{\n      id?: string | null;\n      title?: string | null;\n      field_type?: \"text\" | \"textarea\" | \"number\" | null;\n      placeholder?: string | null;\n      required?: boolean | null;\n      variation_signature?: string | null;\n      position?: number | null;\n      active?: boolean | null;\n    }> | null;\n  };\n  primaryColor: string;\n  whatsappNumber?: string | null;\n  categoryName: string;\n  productDescriptionHtml: string;\n  productTrustItems?: Array<{ icon?: string | null; title?: string | null }> | null;\n  combosData?: any[];\n}\n\nexport default function ProductShowcase({\n  product,\n  primaryColor,\n  whatsappNumber,\n  categoryName,\n  productDescriptionHtml,\n  productTrustItems,\n  combosData,\n}: ProductShowcaseProps) {\n  const galleryImages = useMemo(\n    () => normalizeProductImages(product.product_images, product.image_url),\n    [product.product_images, product.image_url]\n  );\n\n  const defaultImageId = useMemo(() => getPrimaryProductImageId(galleryImages), [galleryImages]);\n  const [activeImageId, setActiveImageId] = useState<string | null>(defaultImageId);\n  const [preferredImageId, setPreferredImageId] = useState<string | null>(null);\n  const [isGalleryHovered, setIsGalleryHovered] = useState(false);\n  const [slideDirection, setSlideDirection] = useState<1 | -1>(1);\n\n  const activeImage = useMemo(() => {\n    if (!galleryImages.length) return null;\n    return (\n      galleryImages.find((image) => image.id === activeImageId) ||\n      galleryImages.find((image) => image.is_primary) ||\n      galleryImages[0]\n    );\n  }, [activeImageId, galleryImages]);\n\n  const imageById = useMemo(() => {\n    const map = new Map<string, ProductImageItem>();\n    galleryImages.forEach((image) => map.set(image.id, image));\n    return map;\n  }, [galleryImages]);\n\n  const imageIndexById = useMemo(() => {\n    const map = new Map<string, number>();\n    galleryImages.forEach((image, index) => map.set(image.id, index));\n    return map;\n  }, [galleryImages]);\n\n  const changeActiveImage = useCallback(\n    (nextImageId: string | null, directionHint?: 1 | -1) => {\n      setActiveImageId((currentImageId) => {\n        if (currentImageId === nextImageId) return currentImageId;\n\n        const currentIndex = currentImageId ? (imageIndexById.get(currentImageId) ?? -1) : -1;\n        const nextIndex = nextImageId ? (imageIndexById.get(nextImageId) ?? -1) : -1;\n        const nextDirection =\n          directionHint ?? (currentIndex >= 0 && nextIndex >= 0 && nextIndex < currentIndex ? -1 : 1);\n\n        setSlideDirection(nextDirection);\n        return nextImageId;\n      });\n    },\n    [imageIndexById]\n  );\n\n  const handlePreferredImageChange = useCallback(\n    (imageId: string | null) => {\n      if (!imageId) {\n        setPreferredImageId(null);\n        changeActiveImage(defaultImageId);\n        return;\n      }\n      if (imageById.has(imageId)) {\n        setPreferredImageId(imageId);\n        changeActiveImage(imageId);\n      }\n    },\n    [changeActiveImage, defaultImageId, imageById]\n  );\n\n  const hasLockedPreferredImage = Boolean(preferredImageId && imageById.has(preferredImageId));\n  const shouldAutoPlay = galleryImages.length > 1 && !hasLockedPreferredImage && !isGalleryHovered;\n\n  useEffect(() => {\n    if (!shouldAutoPlay) return;\n    const timer = window.setInterval(() => {\n      setActiveImageId((currentImageId) => {\n        if (!galleryImages.length) return null;\n        const currentIndex = galleryImages.findIndex((image) => image.id === currentImageId);\n        const fallbackIndex = galleryImages.findIndex((image) => image.id === defaultImageId);\n        const safeIndex = currentIndex >= 0 ? currentIndex : fallbackIndex >= 0 ? fallbackIndex : 0;\n        const nextIndex = (safeIndex + 1) % galleryImages.length;\n        setSlideDirection(1);\n        return galleryImages[nextIndex]?.id || galleryImages[0]?.id || null;\n      });\n    }, 3500);\n\n    return () => window.clearInterval(timer);\n  }, [shouldAutoPlay, galleryImages, defaultImageId]);\n\n  const handleAddToCartCombo = useCallback((combo: any, discountedPrice: number) => {\n    // We emit an event that ProductPurchaseCard can listen to and validate\n    // the current state of the main product before adding the combo.\n    const event = new CustomEvent(\"ktalogu:add_combo\", {\n      detail: { combo, discountedPrice }\n    });\n    window.dispatchEvent(event);\n  }, []);\n\n  return (\n    <div className=\"grid grid-cols-1 md:grid-cols-2 gap-8 lg:gap-16 items-start\">\n      <div\n        className=\"w-full relative group\"\n        onMouseEnter={() => setIsGalleryHovered(true)}\n        onMouseLeave={() => setIsGalleryHovered(false)}\n      >\n        <div className=\"aspect-square w-full bg-white rounded-3xl shadow-sm border border-gray-100 overflow-hidden relative z-10\">\n          <AnimatePresence initial={false} custom={slideDirection}>\n            {activeImage ? (\n              <motion.div\n                key={activeImage.id}\n                custom={slideDirection}\n                initial={{ x: slideDirection > 0 ? \"16%\" : \"-16%\", opacity: 0 }}\n                animate={{ x: \"0%\", opacity: 1 }}\n                exit={{ x: slideDirection > 0 ? \"-16%\" : \"16%\", opacity: 0 }}\n                transition={{\n                  x: { duration: 0.45, ease: [0.22, 1, 0.36, 1] },\n                  opacity: { duration: 0.28, ease: \"easeOut\" },\n                }}\n                className=\"absolute inset-0\"\n              >\n                <Image\n                  src={activeImage.image_url}\n                  alt={product.name}\n                  className=\"w-full h-full object-cover group-hover:scale-105 transition-transform duration-500\"\n                  fill\n                  sizes=\"(max-width: 768px) 100vw, 50vw\"\n                  unoptimized\n                />\n              </motion.div>\n            ) : (\n              <div className=\"w-full h-full flex items-center justify-center bg-gray-50 text-gray-300\">\n                <span className=\"text-sm\">Sem imagem</span>\n              </div>\n            )}\n          </AnimatePresence>\n          {product.track_inventory !== false && product.stock <= 0 && (\n            <div className=\"absolute inset-0 bg-white/60 backdrop-blur-[2px] flex items-center justify-center z-20\">\n              <span className=\"bg-black text-white text-sm font-bold px-4 py-2 rounded-full uppercase tracking-wider\">\n                Esgotado\n              </span>\n            </div>\n          )}\n        </div>\n\n        {galleryImages.length > 1 && (\n          <div className=\"mt-3 grid grid-cols-5 gap-2\">\n            {galleryImages.map((image, index) => {\n              const isActive = image.id === activeImage?.id;\n              return (\n                <button\n                  key={image.id}\n                  type=\"button\"\n                  onClick={() => {\n                    setPreferredImageId(null);\n                    changeActiveImage(image.id);\n                  }}\n                  className={`relative aspect-square overflow-hidden rounded-xl border transition-all ${\n                    isActive\n                      ? \"border-[var(--ks-color-primary)] ring-2 ring-[var(--ks-color-primary)]/25\"\n                      : \"border-gray-200 hover:border-gray-300\"\n                  }`}\n                  title={`Imagem ${index + 1}`}\n                >\n                  <Image\n                    src={image.image_url}\n                    alt={`${product.name} ${index + 1}`}\n                    fill\n                    sizes=\"120px\"\n                    className=\"object-cover\"\n                    unoptimized\n                  />\n                </button>\n              );\n            })}\n          </div>\n        )}\n\n        <div\n          className=\"absolute -inset-4 bg-gradient-to-tr opacity-20 blur-2xl rounded-full -z-0 pointer-events-none\"\n          style={{ backgroundImage: `linear-gradient(to top right, ${primaryColor}, transparent)` }}\n        />\n      </div>\n\n      <div className=\"flex flex-col gap-6 pt-2\">\n        <div>\n          <span className=\"inline-block px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wider bg-gray-100 text-gray-600 mb-3\">\n            {categoryName}\n          </span>\n          {product.unit_type === \"square_meter\" && (\n            <span className=\"inline-block ml-2 px-3 py-1 rounded-full text-xs font-black uppercase tracking-wider bg-white text-gray-900 border border-gray-200 mb-3\">\n              m²\n            </span>\n          )}\n          <h1 className=\"text-3xl md:text-4xl font-black text-gray-900 leading-tight mb-2\">{product.name}</h1>\n          <div className=\"flex items-center gap-2 text-sm text-yellow-500 mb-4\">\n            <div className=\"flex\">\n              <Star size={16} fill=\"currentColor\" />\n              <Star size={16} fill=\"currentColor\" />\n              <Star size={16} fill=\"currentColor\" />\n              <Star size={16} fill=\"currentColor\" />\n              <Star size={16} fill=\"currentColor\" />\n            </div>\n            <span className=\"text-gray-400 font-medium flex items-center gap-1 ml-2\">\n              <CheckCircle2 size={14} className=\"text-[var(--ks-color-success)]\" />\n              Produto verificado\n            </span>\n          </div>\n        </div>\n\n        <div className=\"bg-white p-6 rounded-2xl shadow-sm border border-gray-100\">\n          <ProductPurchaseCard\n            product={product}\n            primaryColor={primaryColor}\n            whatsappNumber={whatsappNumber}\n            onPreferredImageChange={handlePreferredImageChange}\n            trustItems={productTrustItems}\n            combosData={combosData}\n          />\n        </div>\n\n        <div className=\"mt-4\">\n          <h3 className=\"text-lg font-bold text-gray-900 mb-4 border-b border-gray-100 pb-2\">\n            Detalhes do Produto\n          </h3>\n          <div className=\"prose prose-gray max-w-none text-gray-600 leading-relaxed\">\n            {productDescriptionHtml ? (\n              <div dangerouslySetInnerHTML={{ __html: productDescriptionHtml }} />\n            ) : (\n              <p>Este produto não possui descrição detalhada.</p>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\PublicQuoteActions.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\StoreContent.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\StoreLayout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\stories\\StoriesCarousel.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ChevronLeft' is defined but never used.","line":6,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":24,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"ChevronLeft"},"fix":{"range":[159,172],"text":""},"desc":"Remove unused variable \"ChevronLeft\"."}]},{"ruleId":"react-hooks/immutability","severity":2,"message":"Error: Cannot access variable before it is declared\n\n`handleNext` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time.\n\nC:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\stories\\StoriesCarousel.tsx:72:11\n  70 |         if (prev >= 100) {\n  71 |           clearInterval(timer);\n> 72 |           handleNext();\n     |           ^^^^^^^^^^ `handleNext` accessed before it is declared\n  73 |           return 0;\n  74 |         }\n  75 |         return prev + step;\n\nC:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\components\\stories\\StoriesCarousel.tsx:84:3\n   82 |   if (!groups || groups.length === 0) return null;\n   83 |\n>  84 |   const handleNext = () => {\n      |   ^^^^^^^^^^^^^^^^^^^^^^^^^^\n>  85 |     setProgress(0);\n      | ^^^^^^^^^^^^^^^^^^^\n>  86 |     if (!activeGroup) return;\n      …\n      | ^^^^^^^^^^^^^^^^^^^\n>  98 |     }\n      | ^^^^^^^^^^^^^^^^^^^\n>  99 |   };\n      | ^^^^^ `handleNext` is declared here\n  100 |\n  101 |   const handlePrev = () => {\n  102 |     setProgress(0);","line":72,"column":11,"nodeType":null,"endLine":72,"endColumn":21},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has a missing dependency: 'handleNext'. Either include it or remove the dependency array.","line":80,"column":6,"nodeType":"ArrayExpression","endLine":80,"endColumn":63,"suggestions":[{"desc":"Update the dependencies array to be: [activeGroupId, activeItemIndex, activeGroup, activeItem, handleNext]","fix":{"range":[1871,1928],"text":"[activeGroupId, activeItemIndex, activeGroup, activeItem, handleNext]"}}]}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport Image from \"next/image\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { X, ChevronLeft, ChevronRight, ShoppingCart } from \"lucide-react\";\nimport { createPortal } from \"react-dom\";\n\nexport function formatMoney(value: number) {\n  return new Intl.NumberFormat(\"pt-BR\", {\n    style: \"currency\",\n    currency: \"BRL\",\n  }).format(value);\n}\n\ninterface StoryItem {\n  id: string;\n  image_url: string;\n  product_id?: string | null;\n  product?: {\n    id: string;\n    name: string;\n    price: number;\n    price_on_request: boolean;\n    image_url?: string;\n  };\n}\n\ninterface StoryGroup {\n  id: string;\n  title: string;\n  thumbnail_url: string | null;\n  items: StoryItem[];\n}\n\nexport default function StoriesCarousel({ \n  groups, \n  primaryColor,\n  slug\n}: { \n  groups: StoryGroup[],\n  primaryColor: string,\n  slug: string\n}) {\n  const [activeGroupId, setActiveGroupId] = useState<string | null>(null);\n  const [activeItemIndex, setActiveItemIndex] = useState(0);\n  const [mounted, setMounted] = useState(false);\n  const [progress, setProgress] = useState(0);\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const activeGroup = groups.find(g => g.id === activeGroupId);\n  const activeItem = activeGroup?.items?.[activeItemIndex];\n\n  // Story Timer\n  useEffect(() => {\n    if (!activeGroupId || !activeGroup || !activeItem) {\n      setProgress(0);\n      return;\n    }\n\n    const duration = 5000; // 5 seconds per story\n    const interval = 50; // update every 50ms\n    const step = (interval / duration) * 100;\n\n    const timer = setInterval(() => {\n      setProgress(prev => {\n        if (prev >= 100) {\n          clearInterval(timer);\n          handleNext();\n          return 0;\n        }\n        return prev + step;\n      });\n    }, interval);\n\n    return () => clearInterval(timer);\n  }, [activeGroupId, activeItemIndex, activeGroup, activeItem]);\n\n  if (!groups || groups.length === 0) return null;\n\n  const handleNext = () => {\n    setProgress(0);\n    if (!activeGroup) return;\n    if (activeItemIndex < activeGroup.items.length - 1) {\n      setActiveItemIndex(prev => prev + 1);\n    } else {\n      // Find next group\n      const currentGroupIndex = groups.findIndex(g => g.id === activeGroupId);\n      if (currentGroupIndex < groups.length - 1) {\n        setActiveGroupId(groups[currentGroupIndex + 1].id);\n        setActiveItemIndex(0);\n      } else {\n        closeStory();\n      }\n    }\n  };\n\n  const handlePrev = () => {\n    setProgress(0);\n    if (!activeGroup) return;\n    if (activeItemIndex > 0) {\n      setActiveItemIndex(prev => prev - 1);\n    } else {\n      // Find previous group\n      const currentGroupIndex = groups.findIndex(g => g.id === activeGroupId);\n      if (currentGroupIndex > 0) {\n        const prevGroup = groups[currentGroupIndex - 1];\n        setActiveGroupId(prevGroup.id);\n        setActiveItemIndex(prevGroup.items.length - 1);\n      }\n    }\n  };\n\n  const closeStory = () => {\n    setActiveGroupId(null);\n    setActiveItemIndex(0);\n    setProgress(0);\n  };\n\n  return (\n    <div className=\"w-full bg-white pt-2 pb-1 border-b border-gray-100\">\n      <div className=\"flex items-start gap-3 px-3 overflow-x-auto no-scrollbar pb-1 pt-1\">\n        {groups.map(group => (\n          <button \n            key={group.id} \n            className=\"flex flex-col items-center gap-1 shrink-0 group focus:outline-none\"\n            onClick={() => {\n              setActiveGroupId(group.id);\n              setActiveItemIndex(0);\n            }}\n          >\n            <div \n              className=\"w-[66px] h-[66px] rounded-full p-[1.5px] transition-transform group-active:scale-95 story-ring-animated flex items-center justify-center\"\n            >\n              <div className=\"w-full h-full rounded-full border-[2.5px] border-white bg-gray-100 overflow-hidden relative z-10\">\n                {group.thumbnail_url && (\n                  <Image \n                    src={group.thumbnail_url} \n                    alt={group.title} \n                    fill \n                    className=\"object-cover\"\n                  />\n                )}\n              </div>\n            </div>\n            <span className=\"text-[11px] font-medium text-gray-800 w-[70px] truncate text-center mt-0.5\">\n              {group.title}\n            </span>\n          </button>\n        ))}\n      </div>\n\n      {mounted && createPortal(\n        <AnimatePresence>\n          {activeGroupId && activeGroup && (\n            <motion.div \n              initial={{ opacity: 0, y: 50 }}\n              animate={{ opacity: 1, y: 0 }}\n              exit={{ opacity: 0, y: 50 }}\n              className=\"fixed inset-0 z-[99999] bg-black flex flex-col\"\n              style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0 }}\n            >\n              {/* Progress Bars */}\n              <div className=\"absolute top-0 left-0 right-0 p-4 z-20 flex gap-1\">\n                {activeGroup.items.map((item, idx) => (\n                  <div key={item.id} className=\"h-1 flex-1 bg-white/30 rounded-full overflow-hidden\">\n                    <div \n                      className=\"h-full bg-white transition-all duration-75 ease-linear\"\n                      style={{ \n                        width: idx < activeItemIndex ? '100%' : idx === activeItemIndex ? `${progress}%` : '0%' \n                      }}\n                    />\n                  </div>\n                ))}\n              </div>\n\n              {/* Header */}\n              <div className=\"absolute top-6 left-0 right-0 px-4 z-20 flex items-center justify-between\">\n                <div className=\"flex items-center gap-2\">\n                  <div className=\"w-8 h-8 rounded-full overflow-hidden relative border border-white/50\">\n                    {activeGroup.thumbnail_url && (\n                      <Image src={activeGroup.thumbnail_url} alt=\"\" fill className=\"object-cover\" />\n                    )}\n                  </div>\n                  <span className=\"text-white font-semibold text-sm drop-shadow-md\">{activeGroup.title}</span>\n                </div>\n                <button onClick={closeStory} className=\"text-white p-2\">\n                  <X size={24} />\n                </button>\n              </div>\n\n              {/* Content area with tap zones */}\n              <div className=\"flex-1 relative flex items-center justify-center\">\n                {activeItem ? (\n                  <div className=\"absolute inset-0\">\n                    <Image \n                      src={activeItem.image_url} \n                      alt=\"\" \n                      fill \n                      className=\"object-contain\"\n                      priority\n                    />\n                  </div>\n                ) : (\n                  <div className=\"text-white/50 text-sm\">Este story ainda não possui fotos.</div>\n                )}\n\n                {/* Tap zones */}\n                <div className=\"absolute inset-y-0 left-0 w-1/3 z-10\" onClick={handlePrev} />\n                <div className=\"absolute inset-y-0 right-0 w-2/3 z-10\" onClick={handleNext} />\n              </div>\n\n              {/* Bottom Product Card */}\n              {activeItem?.product && (\n                <div className=\"absolute bottom-0 left-0 right-0 p-4 pb-8 z-20 bg-gradient-to-t from-black/90 via-black/50 to-transparent\">\n                  <div className=\"bg-white/95 backdrop-blur-md rounded-2xl p-3 flex items-center gap-3 shadow-[0_8px_30px_rgb(0,0,0,0.2)] mx-2 transform transition-transform hover:scale-[1.02] cursor-pointer\" onClick={() => window.location.href = `/${slug}/p/${activeItem.product!.id}`}>\n                    {/* Product Thumbnail */}\n                    <div className=\"w-12 h-12 rounded-xl bg-gray-100 overflow-hidden shrink-0 border border-gray-200/50 relative\">\n                      {activeItem.product.image_url ? (\n                        <Image src={activeItem.product.image_url} alt={activeItem.product.name} fill className=\"object-cover\" />\n                      ) : (\n                        <div className=\"w-full h-full flex items-center justify-center text-gray-400\">\n                          <ShoppingCart size={16} />\n                        </div>\n                      )}\n                    </div>\n                    \n                    {/* Product Info */}\n                    <div className=\"flex-1 min-w-0\">\n                      <h4 className=\"font-bold text-gray-900 truncate text-sm leading-tight\">{activeItem.product.name}</h4>\n                      <p className=\"text-[var(--ks-color-primary)] font-bold text-sm mt-0.5\">\n                        {activeItem.product.price_on_request ? 'Sob Consulta' : formatMoney(activeItem.product.price)}\n                      </p>\n                    </div>\n                    \n                    {/* Action Button */}\n                    <div \n                      className=\"flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full text-white shadow-sm\"\n                      style={{ backgroundColor: primaryColor }}\n                    >\n                      <ChevronRight size={20} />\n                    </div>\n                  </div>\n                </div>\n              )}\n            </motion.div>\n          )}\n        </AnimatePresence>,\n        document.body\n      )}\n    </div>\n  );\n}\n","usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\context\\CartContext.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\App.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\AIQuoteSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Button.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\DashboardPreview.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\FAQ.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Features.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Footer.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Header.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Hero.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\MobileSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Pricing.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\ProblemSolution.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\ProductionSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\SalesBooster.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\ScrollReveal.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Solution.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Testimonials.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\components\\Workflow.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\index.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\types.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\landing\\vite.config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\catalog-analytics.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\catalog-metadata.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\catalog-stories.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\color-contrast.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\phone.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\price-tiers.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\product-description-rich-text.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\product-images.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\supabase-admin.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\lib\\supabase.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"C:\\Users\\Natan\\Downloads\\ktalogu-main\\apps\\public\\src\\middleware.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]