@akinon/pz-theme 2.0.35-rc.0 → 2.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -28
  2. package/package.json +4 -3
  3. package/src/__tests__/theme-contract.test.ts +130 -0
  4. package/src/blocks/__tests__/text-block.test.tsx +8 -1
  5. package/src/blocks/__tests__/video-block.test.tsx +14 -7
  6. package/src/blocks/counter-block.tsx +44 -56
  7. package/src/blocks/group-block.tsx +23 -22
  8. package/src/blocks/hotspot-block.tsx +171 -80
  9. package/src/blocks/input-block.tsx +15 -17
  10. package/src/blocks/link-block.tsx +18 -21
  11. package/src/blocks/slider-block.tsx +15 -12
  12. package/src/components/with-designer-features.tsx +2 -0
  13. package/src/editor-bridge.ts +160 -0
  14. package/src/hooks/use-designer-features.tsx +30 -37
  15. package/src/placeholder-registry.ts +5 -7
  16. package/src/sections/__tests__/countdown-campaign-banner-section.test.tsx +8 -4
  17. package/src/sections/__tests__/coupon-banner-section.test.tsx +4 -2
  18. package/src/sections/before-after-section.tsx +189 -134
  19. package/src/sections/contact-form-section.tsx +185 -60
  20. package/src/sections/countdown-campaign-banner-section.tsx +73 -27
  21. package/src/sections/coupon-banner-section.tsx +152 -48
  22. package/src/sections/featured-product-spotlight-section.tsx +67 -66
  23. package/src/sections/find-in-store-section.tsx +37 -25
  24. package/src/sections/hover-showcase-section.tsx +221 -89
  25. package/src/sections/image-hotspot-section.tsx +208 -94
  26. package/src/sections/installment-options-section.tsx +268 -259
  27. package/src/sections/notification-banner-section.tsx +111 -25
  28. package/src/sections/order-tracking-lookup-section.tsx +272 -247
  29. package/src/sections/posts-slider-section.tsx +17 -16
  30. package/src/sections/pre-order-launch-banner-section.tsx +31 -50
  31. package/src/sections/section-wrapper.tsx +9 -10
  32. package/src/sections/shipping-threshold-progress-section.tsx +72 -29
  33. package/src/sections/stats-counter-section.tsx +68 -30
  34. package/src/sections/tabs-section.tsx +124 -147
  35. package/src/theme-placeholder-client.tsx +11 -14
  36. package/src/theme-placeholder-wrapper.tsx +42 -51
  37. package/src/theme-section.tsx +27 -34
  38. package/src/utils/__tests__/transform-styles.test.ts +97 -0
  39. package/src/utils/index.ts +9 -0
@@ -1,7 +1,10 @@
1
1
  'use client';
2
2
 
3
3
  import { Image } from '@akinon/next/components/image';
4
- import { useGetInstallmentsQuery, useGetProductByPkQuery } from '@akinon/next/data/client/product';
4
+ import {
5
+ useGetInstallmentsQuery,
6
+ useGetProductByPkQuery
7
+ } from '@akinon/next/data/client/product';
5
8
  import { useLocalization } from '@akinon/next/hooks';
6
9
  import clsx from 'clsx';
7
10
  import React, { useEffect, useMemo, useState } from 'react';
@@ -18,6 +21,8 @@ import {
18
21
  resolveThemeCssVariables
19
22
  } from '../utils';
20
23
 
24
+ import { postToEditor } from '../editor-bridge';
25
+ import { PreviewToEditorMessageType } from '@akinon/theme-contract';
21
26
  interface InstallmentOptionsSectionProps {
22
27
  section: Section;
23
28
  currentBreakpoint?: string;
@@ -215,7 +220,9 @@ const parsePriceValue = (value: unknown): number | null => {
215
220
  };
216
221
 
217
222
  const normalizeCurrencyLabel = (currency: unknown): string => {
218
- const raw = String(currency || '').trim().toUpperCase();
223
+ const raw = String(currency || '')
224
+ .trim()
225
+ .toUpperCase();
219
226
  if (!raw) return 'TL';
220
227
 
221
228
  const map: Record<string, string> = {
@@ -421,8 +428,8 @@ export default function InstallmentOptionsSection({
421
428
  maxWidth === 'narrow'
422
429
  ? 'max-w-4xl'
423
430
  : maxWidth === 'normal'
424
- ? 'max-w-7xl'
425
- : '';
431
+ ? 'max-w-7xl'
432
+ : '';
426
433
  const hasMaxWidth = maxWidth !== 'none';
427
434
 
428
435
  const filteredStyles = Object.fromEntries(
@@ -558,11 +565,13 @@ export default function InstallmentOptionsSection({
558
565
  () => getCollectionProducts(section, isDesigner),
559
566
  [section, isDesigner]
560
567
  );
561
- const collectionProduct = (sectionProducts[0] as ProductLike | undefined) || null;
568
+ const collectionProduct =
569
+ (sectionProducts[0] as ProductLike | undefined) || null;
562
570
  const propertyProductPk = parseProductPk(
563
571
  readStringProperty(section.properties, 'product-pk', '', currentBreakpoint)
564
572
  );
565
- const resolvedProductPk = parseProductPk(collectionProduct?.pk) ?? propertyProductPk;
573
+ const resolvedProductPk =
574
+ parseProductPk(collectionProduct?.pk) ?? propertyProductPk;
566
575
 
567
576
  const shouldFetchProduct = !collectionProduct && resolvedProductPk !== null;
568
577
  // currentData (not data): data is sticky across args, so a cleared
@@ -588,15 +597,17 @@ export default function InstallmentOptionsSection({
588
597
  return fetchedProduct ?? null;
589
598
  }, [collectionProduct, productResponse]);
590
599
 
591
- const usePlaceholderData = isDesigner && resolvedProductPk === null && !product;
600
+ const usePlaceholderData =
601
+ isDesigner && resolvedProductPk === null && !product;
592
602
 
593
603
  const installmentCards = useMemo<InstallmentCard[]>(() => {
594
604
  if (usePlaceholderData) {
595
605
  return PLACEHOLDER_INSTALLMENTS;
596
606
  }
597
- return ((installmentsData as InstallmentResponse | undefined)?.results || []).filter(
598
- (card): card is InstallmentCard =>
599
- Boolean(card && card.slug && Array.isArray(card.installments))
607
+ return (
608
+ (installmentsData as InstallmentResponse | undefined)?.results || []
609
+ ).filter((card): card is InstallmentCard =>
610
+ Boolean(card && card.slug && Array.isArray(card.installments))
600
611
  );
601
612
  }, [installmentsData, usePlaceholderData]);
602
613
 
@@ -650,9 +661,7 @@ export default function InstallmentOptionsSection({
650
661
  currentBreakpoint
651
662
  );
652
663
 
653
- const productName = String(
654
- product?.name || PLACEHOLDER_PRODUCT.name
655
- ).trim();
664
+ const productName = String(product?.name || PLACEHOLDER_PRODUCT.name).trim();
656
665
  const productUrl = String(
657
666
  product?.absolute_url || PLACEHOLDER_PRODUCT.absolute_url
658
667
  ).trim();
@@ -672,92 +681,75 @@ export default function InstallmentOptionsSection({
672
681
  currentBreakpoint={currentBreakpoint}
673
682
  onMoveUp={() => {
674
683
  if (window.parent) {
675
- window.parent.postMessage(
676
- {
677
- type: 'MOVE_BLOCK_UP',
678
- data: { placeholderId, sectionId: section.id, blockId: block.id }
679
- },
680
- '*'
681
- );
684
+ postToEditor({
685
+ type: 'MOVE_BLOCK_UP',
686
+ data: { placeholderId, sectionId: section.id, blockId: block.id }
687
+ });
682
688
  }
683
689
  }}
684
690
  onMoveDown={() => {
685
691
  if (window.parent) {
686
- window.parent.postMessage(
687
- {
688
- type: 'MOVE_BLOCK_DOWN',
689
- data: { placeholderId, sectionId: section.id, blockId: block.id }
690
- },
691
- '*'
692
- );
692
+ postToEditor({
693
+ type: 'MOVE_BLOCK_DOWN',
694
+ data: { placeholderId, sectionId: section.id, blockId: block.id }
695
+ });
693
696
  }
694
697
  }}
695
698
  onDuplicate={() => {
696
699
  if (window.parent) {
697
- window.parent.postMessage(
698
- {
699
- type: 'DUPLICATE_BLOCK',
700
- data: { placeholderId, sectionId: section.id, blockId: block.id }
701
- },
702
- '*'
703
- );
700
+ postToEditor({
701
+ type: 'DUPLICATE_BLOCK',
702
+ data: { placeholderId, sectionId: section.id, blockId: block.id }
703
+ });
704
704
  }
705
705
  }}
706
706
  onToggleVisibility={() => {
707
707
  if (window.parent) {
708
- window.parent.postMessage(
709
- {
710
- type: 'TOGGLE_BLOCK_VISIBILITY',
711
- data: { placeholderId, sectionId: section.id, blockId: block.id }
712
- },
713
- '*'
714
- );
708
+ postToEditor({
709
+ type: 'TOGGLE_BLOCK_VISIBILITY',
710
+ data: { placeholderId, sectionId: section.id, blockId: block.id }
711
+ });
715
712
  }
716
713
  }}
717
714
  onDelete={() => {
718
715
  if (window.parent) {
719
- window.parent.postMessage(
720
- {
721
- type: 'DELETE_BLOCK',
722
- data: { placeholderId, sectionId: section.id, blockId: block.id }
723
- },
724
- '*'
725
- );
716
+ postToEditor({
717
+ type: 'DELETE_BLOCK',
718
+ data: { placeholderId, sectionId: section.id, blockId: block.id }
719
+ });
726
720
  }
727
721
  }}
728
722
  onRename={(newLabel) => {
729
723
  if (window.parent) {
730
- window.parent.postMessage(
731
- {
732
- type: 'RENAME_BLOCK',
733
- data: {
734
- placeholderId,
735
- sectionId: section.id,
736
- blockId: block.id,
737
- label: newLabel
738
- }
739
- },
740
- '*'
741
- );
724
+ postToEditor({
725
+ type: 'RENAME_BLOCK',
726
+ data: {
727
+ placeholderId,
728
+ sectionId: section.id,
729
+ blockId: block.id,
730
+ label: newLabel
731
+ }
732
+ });
742
733
  }
743
734
  }}
744
735
  />
745
736
  );
746
737
 
747
- const postBlockAction = (type: string, blockId: string, label?: string) => {
738
+ const postBlockAction = (
739
+ type: PreviewToEditorMessageType,
740
+ blockId: string,
741
+ label?: string
742
+ ) => {
748
743
  if (!window.parent) return;
749
- window.parent.postMessage(
750
- {
751
- type,
752
- data: {
753
- placeholderId,
754
- sectionId: section.id,
755
- blockId,
756
- ...(label ? { label } : {})
757
- }
758
- },
759
- '*'
760
- );
744
+ postToEditor({
745
+ type,
746
+ data: {
747
+ placeholderId,
748
+ sectionId: section.id,
749
+ blockId,
750
+ ...(label ? { label } : {})
751
+ }
752
+ });
761
753
  };
762
754
 
763
755
  const renderSelectableWrapper = (
@@ -836,67 +828,67 @@ export default function InstallmentOptionsSection({
836
828
  renderSelectableWrapper(
837
829
  productCardWrapperBlock,
838
830
  <>
839
- <div className="mb-4 flex items-center justify-between gap-3">
840
- <p className="m-0 text-xs font-bold uppercase tracking-[0.16em] text-[#64748b]">
841
- Selected Product
842
- </p>
843
- {productUrl && productUrl !== '#' && (
844
- <a
845
- href={productUrl}
846
- className="text-xs font-semibold text-[#0f172a] underline underline-offset-4"
847
- >
848
- View product
849
- </a>
850
- )}
851
- </div>
852
-
853
- <div
854
- className="rounded-[14px] border bg-white p-3"
855
- style={{
856
- borderColor: productCardBorder
857
- }}
858
- >
859
- <div className="relative mb-3 aspect-square w-full overflow-hidden rounded-[12px] bg-[#f8fafc]">
860
- {productImage ? (
861
- <Image
862
- src={productImage}
863
- alt={productName}
864
- fill
865
- sizes="280px"
866
- aspectRatio={1}
867
- imageClassName="object-cover"
868
- />
869
- ) : (
870
- <div className="flex h-full w-full items-center justify-center bg-[#e2e8f0] text-sm font-semibold tracking-wide text-[#64748b]">
871
- Product Preview
872
- </div>
831
+ <div className="mb-4 flex items-center justify-between gap-3">
832
+ <p className="m-0 text-xs font-bold uppercase tracking-[0.16em] text-[#64748b]">
833
+ Selected Product
834
+ </p>
835
+ {productUrl && productUrl !== '#' && (
836
+ <a
837
+ href={productUrl}
838
+ className="text-xs font-semibold text-[#0f172a] underline underline-offset-4"
839
+ >
840
+ View product
841
+ </a>
873
842
  )}
874
843
  </div>
875
844
 
876
- <div className="flex min-w-0 flex-col gap-2">
877
- <p
878
- className="m-0 overflow-hidden text-[15px] font-medium leading-[1.45] text-[#0f172a]"
879
- style={{
880
- display: '-webkit-box',
881
- WebkitLineClamp: isMobile ? 3 : 2,
882
- WebkitBoxOrient: 'vertical'
883
- }}
884
- >
885
- {productName}
886
- </p>
845
+ <div
846
+ className="rounded-[14px] border bg-white p-3"
847
+ style={{
848
+ borderColor: productCardBorder
849
+ }}
850
+ >
851
+ <div className="relative mb-3 aspect-square w-full overflow-hidden rounded-[12px] bg-[#f8fafc]">
852
+ {productImage ? (
853
+ <Image
854
+ src={productImage}
855
+ alt={productName}
856
+ fill
857
+ sizes="280px"
858
+ aspectRatio={1}
859
+ imageClassName="object-cover"
860
+ />
861
+ ) : (
862
+ <div className="flex h-full w-full items-center justify-center bg-[#e2e8f0] text-sm font-semibold tracking-wide text-[#64748b]">
863
+ Product Preview
864
+ </div>
865
+ )}
866
+ </div>
887
867
 
888
- <div className="flex flex-wrap items-center gap-2">
889
- {productRetailPrice && (
890
- <span className="text-sm font-normal text-[#94a3b8] line-through">
891
- {productRetailPrice}
868
+ <div className="flex min-w-0 flex-col gap-2">
869
+ <p
870
+ className="m-0 overflow-hidden text-[15px] font-medium leading-[1.45] text-[#0f172a]"
871
+ style={{
872
+ display: '-webkit-box',
873
+ WebkitLineClamp: isMobile ? 3 : 2,
874
+ WebkitBoxOrient: 'vertical'
875
+ }}
876
+ >
877
+ {productName}
878
+ </p>
879
+
880
+ <div className="flex flex-wrap items-center gap-2">
881
+ {productRetailPrice && (
882
+ <span className="text-sm font-normal text-[#94a3b8] line-through">
883
+ {productRetailPrice}
884
+ </span>
885
+ )}
886
+ <span className="text-[17px] font-semibold text-[#0f172a]">
887
+ {productPrice}
892
888
  </span>
893
- )}
894
- <span className="text-[17px] font-semibold text-[#0f172a]">
895
- {productPrice}
896
- </span>
889
+ </div>
897
890
  </div>
898
891
  </div>
899
- </div>
900
892
  </>,
901
893
  {
902
894
  className: 'min-w-0 border p-4',
@@ -912,143 +904,160 @@ export default function InstallmentOptionsSection({
912
904
  {renderSelectableWrapper(
913
905
  installmentPanelWrapperBlock,
914
906
  <>
915
- {isLoading && !usePlaceholderData ? (
916
- <div className="flex min-h-[220px] items-center justify-center">
917
- <LoaderSpinner />
918
- </div>
919
- ) : installmentCards.length > 0 ? (
920
- <>
921
- {showCardLogos && (
922
- <div className="flex flex-wrap gap-2">
923
- {installmentCards.map((card) => {
924
- const isActive = activeCard?.slug === card.slug;
925
- return renderSelectableWrapper(
926
- logoCardWrapperBlock
927
- ? {
928
- ...logoCardWrapperBlock,
929
- id: `${logoCardWrapperBlock.id}-clone-${card.pk}`,
930
- styleSourceId:
931
- logoCardWrapperBlock.styleSourceId ||
932
- logoCardWrapperBlock.id
933
- }
934
- : null,
935
- (
936
- <button
937
- key={card.pk}
938
- type="button"
939
- onClick={() => setActiveCardSlug(card.slug)}
940
- className={clsx(
941
- 'flex min-w-[72px] flex-1 items-center justify-center px-3 py-2 transition-colors lg:min-w-[96px] lg:flex-none',
942
- !logoCardWrapperBlock && 'border'
943
- )}
944
- style={{
945
- backgroundColor: logoCardWrapperBlock
946
- ? 'transparent'
947
- : isActive
907
+ {isLoading && !usePlaceholderData ? (
908
+ <div className="flex min-h-[220px] items-center justify-center">
909
+ <LoaderSpinner />
910
+ </div>
911
+ ) : installmentCards.length > 0 ? (
912
+ <>
913
+ {showCardLogos && (
914
+ <div className="flex flex-wrap gap-2">
915
+ {installmentCards.map((card) => {
916
+ const isActive = activeCard?.slug === card.slug;
917
+ return renderSelectableWrapper(
918
+ logoCardWrapperBlock
919
+ ? {
920
+ ...logoCardWrapperBlock,
921
+ id: `${logoCardWrapperBlock.id}-clone-${card.pk}`,
922
+ styleSourceId:
923
+ logoCardWrapperBlock.styleSourceId ||
924
+ logoCardWrapperBlock.id
925
+ }
926
+ : null,
927
+ <button
928
+ key={card.pk}
929
+ type="button"
930
+ onClick={() => setActiveCardSlug(card.slug)}
931
+ className={clsx(
932
+ 'flex min-w-[72px] flex-1 items-center justify-center px-3 py-2 transition-colors lg:min-w-[96px] lg:flex-none',
933
+ !logoCardWrapperBlock && 'border'
934
+ )}
935
+ style={{
936
+ backgroundColor: logoCardWrapperBlock
937
+ ? 'transparent'
938
+ : isActive
948
939
  ? logoCardActiveBackground
949
940
  : logoCardBackground,
950
- borderColor: logoCardWrapperBlock
951
- ? 'transparent'
952
- : isActive
941
+ borderColor: logoCardWrapperBlock
942
+ ? 'transparent'
943
+ : isActive
953
944
  ? logoCardActiveBorder
954
945
  : logoCardBorder,
955
- borderRadius: logoCardWrapperBlock ? '0px' : '12px'
956
- }}
957
- >
958
- {card.card_type?.logo ? (
959
- <Image
960
- src={card.card_type.logo}
961
- alt={card.name}
962
- width={56}
963
- height={20}
964
- imageClassName={clsx(
965
- 'h-auto max-h-5 w-auto max-w-full object-contain',
966
- !isActive && 'grayscale'
967
- )}
968
- />
969
- ) : (
970
- <span className="text-xs font-bold uppercase tracking-[0.16em] text-[#0f172a]">
971
- {getFallbackCardLabel(card.name)}
972
- </span>
973
- )}
974
- </button>
975
- ),
976
- {
977
- keyOverride: `installment-logo-card-${card.pk}`
978
- }
979
- );
980
- })}
981
- </div>
982
- )}
983
-
984
- {renderSelectableWrapper(
985
- tableWrapperBlock,
986
- <div
987
- className={clsx(
988
- 'overflow-hidden rounded-[12px]',
989
- !tableWrapperBlock && 'border'
990
- )}
991
- style={{
992
- borderColor: tableRowBorderColor
993
- }}
994
- >
995
- <div
996
- className="grid grid-cols-3 gap-4 border-b px-4 py-3 text-xs font-bold uppercase tracking-[0.14em]"
997
- style={{
998
- borderColor: tableRowBorderColor,
999
- color: tableHeaderColor,
1000
- backgroundColor: '#f8fafc'
1001
- }}
1002
- >
1003
- <span>{t('product.number_of_installments')}</span>
1004
- <span className="text-right">{t('product.monthly_amount')}</span>
1005
- <span className="text-right">{t('product.total_amount')}</span>
1006
- </div>
946
+ borderRadius: logoCardWrapperBlock
947
+ ? '0px'
948
+ : '12px'
949
+ }}
950
+ >
951
+ {card.card_type?.logo ? (
952
+ <Image
953
+ src={card.card_type.logo}
954
+ alt={card.name}
955
+ width={56}
956
+ height={20}
957
+ imageClassName={clsx(
958
+ 'h-auto max-h-5 w-auto max-w-full object-contain',
959
+ !isActive && 'grayscale'
960
+ )}
961
+ />
962
+ ) : (
963
+ <span className="text-xs font-bold uppercase tracking-[0.16em] text-[#0f172a]">
964
+ {getFallbackCardLabel(card.name)}
965
+ </span>
966
+ )}
967
+ </button>,
968
+ {
969
+ keyOverride: `installment-logo-card-${card.pk}`
970
+ }
971
+ );
972
+ })}
973
+ </div>
974
+ )}
1007
975
 
1008
- <div className="overflow-x-auto">
1009
- {(activeCard?.installments || []).map((installment, index) => {
1010
- const isHighlighted = highlightBestOption && index === 0;
1011
- return (
1012
- <div
1013
- key={`${activeCard?.slug || 'card'}-${installment.installment_count}`}
1014
- className="grid grid-cols-3 gap-4 border-b px-4 py-3 last:border-b-0"
1015
- style={{
1016
- borderColor: tableRowBorderColor,
1017
- backgroundColor: isHighlighted
1018
- ? highlightRowBackground
1019
- : panelBackground,
1020
- color: isHighlighted ? highlightRowText : '#0f172a'
1021
- }}
1022
- >
1023
- <div className="min-w-0 text-sm font-semibold">
1024
- {installment.installment_count === 1
1025
- ? t('product.cash')
1026
- : `${installment.installment_count} ${t('product.installment')}`}
1027
- </div>
1028
- <div className="text-right text-sm font-semibold">
1029
- <Price value={installment.single_installment_amount} />
1030
- </div>
1031
- <div className="text-right text-sm font-semibold">
1032
- <Price value={installment.total_amount} />
1033
- </div>
1034
- </div>
1035
- );
1036
- })}
1037
- </div>
1038
- </div>,
1039
- {
1040
- keyOverride: 'installment-table-wrapper'
1041
- }
1042
- )}
1043
- </>
1044
- ) : (
1045
- <div
1046
- className="flex min-h-[220px] items-center justify-center rounded-[12px] border border-dashed px-6 text-center text-sm font-medium text-[#64748b]"
1047
- style={{ borderColor: panelBorder }}
1048
- >
1049
- {emptyStateText}
1050
- </div>
1051
- )}
976
+ {renderSelectableWrapper(
977
+ tableWrapperBlock,
978
+ <div
979
+ className={clsx(
980
+ 'overflow-hidden rounded-[12px]',
981
+ !tableWrapperBlock && 'border'
982
+ )}
983
+ style={{
984
+ borderColor: tableRowBorderColor
985
+ }}
986
+ >
987
+ <div
988
+ className="grid grid-cols-3 gap-4 border-b px-4 py-3 text-xs font-bold uppercase tracking-[0.14em]"
989
+ style={{
990
+ borderColor: tableRowBorderColor,
991
+ color: tableHeaderColor,
992
+ backgroundColor: '#f8fafc'
993
+ }}
994
+ >
995
+ <span>{t('product.number_of_installments')}</span>
996
+ <span className="text-right">
997
+ {t('product.monthly_amount')}
998
+ </span>
999
+ <span className="text-right">
1000
+ {t('product.total_amount')}
1001
+ </span>
1002
+ </div>
1003
+
1004
+ <div className="overflow-x-auto">
1005
+ {(activeCard?.installments || []).map(
1006
+ (installment, index) => {
1007
+ const isHighlighted =
1008
+ highlightBestOption && index === 0;
1009
+ return (
1010
+ <div
1011
+ key={`${activeCard?.slug || 'card'}-${
1012
+ installment.installment_count
1013
+ }`}
1014
+ className="grid grid-cols-3 gap-4 border-b px-4 py-3 last:border-b-0"
1015
+ style={{
1016
+ borderColor: tableRowBorderColor,
1017
+ backgroundColor: isHighlighted
1018
+ ? highlightRowBackground
1019
+ : panelBackground,
1020
+ color: isHighlighted
1021
+ ? highlightRowText
1022
+ : '#0f172a'
1023
+ }}
1024
+ >
1025
+ <div className="min-w-0 text-sm font-semibold">
1026
+ {installment.installment_count === 1
1027
+ ? t('product.cash')
1028
+ : `${installment.installment_count} ${t(
1029
+ 'product.installment'
1030
+ )}`}
1031
+ </div>
1032
+ <div className="text-right text-sm font-semibold">
1033
+ <Price
1034
+ value={
1035
+ installment.single_installment_amount
1036
+ }
1037
+ />
1038
+ </div>
1039
+ <div className="text-right text-sm font-semibold">
1040
+ <Price value={installment.total_amount} />
1041
+ </div>
1042
+ </div>
1043
+ );
1044
+ }
1045
+ )}
1046
+ </div>
1047
+ </div>,
1048
+ {
1049
+ keyOverride: 'installment-table-wrapper'
1050
+ }
1051
+ )}
1052
+ </>
1053
+ ) : (
1054
+ <div
1055
+ className="flex min-h-[220px] items-center justify-center rounded-[12px] border border-dashed px-6 text-center text-sm font-medium text-[#64748b]"
1056
+ style={{ borderColor: panelBorder }}
1057
+ >
1058
+ {emptyStateText}
1059
+ </div>
1060
+ )}
1052
1061
  </>,
1053
1062
  {
1054
1063
  className: 'flex min-w-0 flex-col gap-4 border p-4 lg:p-5',