contentoh-components-library 21.6.57 → 21.6.59

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 (49) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/dist/components/pages/CreatorsProductEdition/CreatorsProductEditionView.js +90 -135
  3. package/dist/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +148 -162
  4. package/dist/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +16 -0
  5. package/dist/components/pages/CreatorsProductEdition/mocks/mockApi.js +1 -0
  6. package/dist/components/pages/CreatorsProductEdition/sections/ImageGallery.js +433 -0
  7. package/dist/components/pages/CreatorsProductEdition/sections/NoticeDialog.js +155 -0
  8. package/dist/components/pages/CreatorsProductEdition/sections/RejectDialog.js +201 -0
  9. package/dist/components/pages/CreatorsProductEdition/sections/VersionDrawer.js +615 -0
  10. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +3 -31
  11. package/dist/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +55 -30
  12. package/dist/components/pages/CreatorsProductEdition/stories/products.js +923 -0
  13. package/dist/components/pages/CreatorsProductEdition/styles.js +27 -7
  14. package/dist/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +2 -1
  15. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.context.js +325 -10
  16. package/dist/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -2
  17. package/dist/components/pages/ProviderProductEdition/index.js +486 -231
  18. package/dist/index.js +13 -0
  19. package/package.json +1 -1
  20. package/src/components/pages/CreatorsProductEdition/CreatorsProductEditionView.jsx +777 -0
  21. package/src/components/pages/CreatorsProductEdition/constants.js +21 -0
  22. package/src/components/pages/CreatorsProductEdition/hooks/useCreatorsProductEdition.js +2238 -0
  23. package/src/components/pages/CreatorsProductEdition/index.js +19 -0
  24. package/src/components/pages/CreatorsProductEdition/mocks/api.fixtures.js +357 -0
  25. package/src/components/pages/CreatorsProductEdition/mocks/mockApi.js +144 -0
  26. package/src/components/pages/CreatorsProductEdition/sections/CommentBox.jsx +183 -0
  27. package/src/components/pages/CreatorsProductEdition/sections/ImageGallery.jsx +269 -0
  28. package/src/components/pages/CreatorsProductEdition/sections/ImagePanel.jsx +332 -0
  29. package/src/components/pages/CreatorsProductEdition/sections/ImagesSection.jsx +385 -0
  30. package/src/components/pages/CreatorsProductEdition/sections/NoticeDialog.jsx +111 -0
  31. package/src/components/pages/CreatorsProductEdition/sections/ProductHeader.jsx +305 -0
  32. package/src/components/pages/CreatorsProductEdition/sections/ProductMedia.jsx +101 -0
  33. package/src/components/pages/CreatorsProductEdition/sections/RejectDialog.jsx +125 -0
  34. package/src/components/pages/CreatorsProductEdition/sections/RetailerMenu.jsx +129 -0
  35. package/src/components/pages/CreatorsProductEdition/sections/ServiceActions.jsx +382 -0
  36. package/src/components/pages/CreatorsProductEdition/sections/StatusChip.jsx +37 -0
  37. package/src/components/pages/CreatorsProductEdition/sections/TabsBar.jsx +187 -0
  38. package/src/components/pages/CreatorsProductEdition/sections/VariantsPanel.jsx +311 -0
  39. package/src/components/pages/CreatorsProductEdition/sections/VersionDrawer.jsx +357 -0
  40. package/src/components/pages/CreatorsProductEdition/sections/tokens.js +63 -0
  41. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEdition.stories.js +111 -0
  42. package/src/components/pages/CreatorsProductEdition/stories/CreatorsProductEditionAPI.stories.js +85 -0
  43. package/src/components/pages/CreatorsProductEdition/stories/products.js +966 -0
  44. package/src/components/pages/CreatorsProductEdition/styles.js +178 -0
  45. package/src/components/pages/ProviderProductEdition/ProviderProductEdition.stories.js +1 -0
  46. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.context.jsx +201 -0
  47. package/src/components/pages/ProviderProductEdition/context/provider-product-edition.reducer.js +12 -0
  48. package/src/components/pages/ProviderProductEdition/index.js +228 -7
  49. package/src/index.js +1 -0
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { useEffect, useRef, useState } from "react";
2
2
 
3
3
  import axios from "axios";
4
4
  import { useDropzone } from "react-dropzone";
@@ -21,6 +21,8 @@ import {
21
21
  getServicesData,
22
22
  } from "../../../global-files/data";
23
23
  import { useCloseModal } from "../../../global-files/customHooks";
24
+ import { useDigitalAssets } from "../../../global-files/digital-assets/useDigitalAssets";
25
+ import { countUnsavedAssets } from "../../../global-files/digital-assets/assets.reducer";
24
26
 
25
27
  import { Button } from "../../atoms/GeneralButton";
26
28
  import { GenericModal } from "../../atoms/GenericModal";
@@ -30,12 +32,14 @@ import { ButtonV2 } from "../../atoms/ButtonV2";
30
32
 
31
33
  import { GalleryElement } from "../../molecules/GalleryElement";
32
34
  import { GalleryHeader } from "../../molecules/GalleryHeader";
35
+ import { GalleryTrayTabs, TRAYS } from "../../molecules/GalleryTrayTabs";
33
36
  import { HeaderTop } from "../../molecules/HeaderTop";
34
37
  import { TagAndInput } from "../../molecules/TagAndInput/index";
35
38
 
36
39
  import { FullProductNameHeader } from "../../organisms/FullProductNameHeader";
37
40
  import { FullTabsMenu } from "../../organisms/FullTabsMenu";
38
41
  import { ImageDataTable } from "../../organisms/ImageDataTable";
42
+ import { DigitalAssetsTray } from "../../organisms/DigitalAssetsTray";
39
43
  import { ImagePreviewer } from "../../organisms/ImagePreviewer";
40
44
  import { InputGroup } from "../../organisms/InputGroup";
41
45
  import { LinkVariantsModal } from "../../organisms/LinkVariantsModal";
@@ -67,6 +71,14 @@ const ProviderProductEditionView = ({
67
71
  showSurvey,
68
72
  company,
69
73
  addToCart,
74
+ /*
75
+ Activos digitales (360, video, gif, pdf). Apagado por omisión, igual que en
76
+ RetailerProductEdition: la interfaz ya está, pero mientras el backend no
77
+ devuelva `assets` en los servicios del producto la bandeja solo muestra lo
78
+ que el usuario acaba de soltar. La app que consuma la librería lo enciende
79
+ cuando el backend esté listo.
80
+ */
81
+ enableDigitalAssets = false,
70
82
  }) => {
71
83
  const {
72
84
  state,
@@ -75,6 +87,8 @@ const ProviderProductEditionView = ({
75
87
  saveDescriptions,
76
88
  updateImages,
77
89
  deleteImages,
90
+ saveAssets,
91
+ registerUploadedAssets,
78
92
  handleOnDownloadImages,
79
93
  createComment,
80
94
  } = useProviderProductEdition();
@@ -122,6 +136,49 @@ const ProviderProductEditionView = ({
122
136
  Imágenes: null,
123
137
  });
124
138
 
139
+ /*
140
+ Bandeja de activos digitales. La lista vive en el reducer de la ficha
141
+ (`state.assets_values`), igual que las imágenes, para que el guardado del
142
+ contexto pueda leerla. El hook solo aporta validación y ciclo de vida.
143
+ */
144
+ const [assetNotices, setAssetNotices] = useState([]);
145
+ const digitalAssets = useDigitalAssets({
146
+ assets: state.assets_values,
147
+ dispatch,
148
+ notices: assetNotices,
149
+ setNotices: setAssetNotices,
150
+ // Con los dos, la subida a S3 arranca sola al soltar los archivos. Sin
151
+ // ellos —en Storybook, por ejemplo— la bandeja funciona igual pero no sube.
152
+ articleId: state.product?.id_article ?? null,
153
+ token,
154
+ // Al terminar la subida el activo queda registrado solo, sin esperar al
155
+ // botón de guardar: con la barra llena, nadie supone que falta un paso.
156
+ onUploaded: (uploaded) => registerUploadedAssets(token, uploaded),
157
+ });
158
+ const [activeTray, setActiveTray] = useState(TRAYS.IMAGES);
159
+ const currentTray = enableDigitalAssets ? activeTray : TRAYS.IMAGES;
160
+
161
+ /*
162
+ De qué artículo se cargaron ya los activos. `state.services` se recalcula
163
+ cada vez que cambia la cadena activa, pero los activos son generales: no
164
+ dependen de la cadena, así que recargarlos borraría lo que el usuario
165
+ acaba de soltar y todavía no guarda. Por eso se cargan una sola vez por
166
+ artículo.
167
+ */
168
+ const assetsLoadedFor = useRef(null);
169
+
170
+ const handleOnSetSelectedAssets = (assets) =>
171
+ dispatch({
172
+ type: "SET_SELECTED_ASSETS",
173
+ payload: assets,
174
+ });
175
+
176
+ const handleOnSetAllAssetsChecked = (isChecked) =>
177
+ dispatch({
178
+ type: "TOGGLE_CHECK_ALL_ASSETS",
179
+ payload: isChecked,
180
+ });
181
+
125
182
  const [showVersionSelector, setShowVersionSelector] =
126
183
  useCloseModal("version-selector");
127
184
  const { getRootProps, getInputProps, isDragActive, open } = useDropzone({
@@ -366,6 +423,19 @@ const ProviderProductEditionView = ({
366
423
 
367
424
  dispatch({ type: "SET_SERVICES_DATA", payload: servicesDataRes });
368
425
 
426
+ /*
427
+ Activos digitales. `services[2].assets` todavía no existe en la
428
+ respuesta del backend: hasta que exista, esto no hace nada, y el día
429
+ que llegue la bandeja se llena sola sin tocar nada más.
430
+ */
431
+ if (
432
+ Array.isArray(services[2].assets) &&
433
+ assetsLoadedFor.current !== id_article
434
+ ) {
435
+ assetsLoadedFor.current = id_article;
436
+ digitalAssets.replaceAll(services[2].assets);
437
+ }
438
+
369
439
  dispatch({ type: "SET_PERCENTAGES", payload: percentages });
370
440
 
371
441
  const activePercentage =
@@ -594,6 +664,27 @@ const ProviderProductEditionView = ({
594
664
  }
595
665
  };
596
666
 
667
+ /*
668
+ Guarda los activos y fuerza la recarga si algo se escribió.
669
+
670
+ Esta pantalla no tiene una función `loadData` reutilizable como
671
+ RetailerProductEdition: la carga de servicios vive en un `useEffect` que
672
+ solo se dispara por sus dependencias. `linkNonce` ya existe ahí para eso
673
+ mismo —forzar un refetch sin recargar la página—, así que se reutiliza en
674
+ vez de duplicar el mecanismo.
675
+
676
+ La recarga no es opcional: un activo recién creado sigue en memoria con su
677
+ id temporal y sin el id de la base. Sin volver a leer, el siguiente
678
+ guardado lo mandaría otra vez como nuevo y se duplicaría.
679
+ */
680
+ const handleOnSaveAssets = async () => {
681
+ const guardado = await saveAssets(token);
682
+ if (!guardado) return;
683
+
684
+ assetsLoadedFor.current = null;
685
+ setLinkNonce((n) => n + 1);
686
+ };
687
+
597
688
  const handleOnApproveAll = () => {
598
689
  const currentOrigin = origin[state.active_tab];
599
690
 
@@ -671,7 +762,98 @@ const ProviderProductEditionView = ({
671
762
  setShowRejectModal(true);
672
763
  };
673
764
 
765
+ /*
766
+ Descarga de activos: los seleccionados, o todos si no hay selección — el
767
+ mismo criterio que usa la descarga de imágenes.
768
+
769
+ Solo se descarga lo que ya está en S3. Un activo recién soltado vive como
770
+ referencia al archivo del disco del usuario: "descargarlo" le devolvería el
771
+ archivo que él mismo acaba de elegir, lo cual no tiene sentido.
772
+ */
773
+ const handleOnDownloadAssets = () => {
774
+ const seleccion = state.selected_assets.length
775
+ ? state.selected_assets
776
+ : state.assets_values;
777
+
778
+ const descargables = seleccion.filter((asset) => asset.id && asset.url);
779
+
780
+ if (descargables.length === 0) {
781
+ dispatch({
782
+ type: "SET_MODAL",
783
+ payload: {
784
+ show: true,
785
+ title: "Nada que descargar",
786
+ message:
787
+ "Los activos seleccionados todavía no se han guardado. Guárdalos primero.",
788
+ image: warningIcon,
789
+ },
790
+ });
791
+ return;
792
+ }
793
+
794
+ descargables.forEach((asset) => saveAs(asset.url, asset.name));
795
+ };
796
+
797
+ /*
798
+ Borrado en lote de activos.
799
+
800
+ A diferencia de las imágenes, que se borran contra el backend en el
801
+ momento, los activos se quitan de la bandeja y su baja viaja en el mismo
802
+ PUT del guardado (`deleteAssets`). Es lo que permite al usuario deshacerse
803
+ de un borrado por error: basta con no guardar. El mensaje lo dice, para que
804
+ nadie se quede pensando que ya se aplicó.
805
+ */
806
+ const handleOnAskToDeleteAssets = () => {
807
+ if (state.selected_assets.length === 0) {
808
+ dispatch({
809
+ type: "SET_MODAL",
810
+ payload: {
811
+ show: true,
812
+ title: "Eliminar activos digitales",
813
+ message: "No has seleccionado ningún activo para eliminar.",
814
+ image: warningIcon,
815
+ },
816
+ });
817
+ return;
818
+ }
819
+
820
+ const seleccionados = state.selected_assets;
821
+
822
+ dispatch({
823
+ type: "SET_MODAL",
824
+ payload: {
825
+ show: true,
826
+ title: "Eliminar activos digitales",
827
+ message: `Se quitarán ${seleccionados.length} activo(s) de la bandeja. El borrado se aplica al guardar.`,
828
+ image: warningIcon,
829
+ buttons: [
830
+ {
831
+ text: "Cancelar",
832
+ buttonType: "general-white-button",
833
+ action: () =>
834
+ dispatch({ type: "SET_MODAL", payload: { show: false } }),
835
+ },
836
+ {
837
+ text: "Eliminar",
838
+ buttonType: "general-button-default",
839
+ action: () => {
840
+ dispatch({ type: "SET_MODAL", payload: { show: false } });
841
+ seleccionados.forEach((asset) =>
842
+ digitalAssets.removeAsset(asset),
843
+ );
844
+ },
845
+ },
846
+ ],
847
+ },
848
+ });
849
+ };
850
+
674
851
  const handleOnAskToDeleteImages = () => {
852
+ if (currentTray === TRAYS.ASSETS) {
853
+ handleOnAskToDeleteAssets();
854
+ return;
855
+ }
856
+
675
857
  if (state.selected_images.length === 0) {
676
858
  dispatch({
677
859
  type: "SET_MODAL",
@@ -847,6 +1029,13 @@ const ProviderProductEditionView = ({
847
1029
  break;
848
1030
  }
849
1031
  case "Imágenes":
1032
+ // Se guarda la bandeja que el usuario tiene a la vista: guardar las
1033
+ // dos de golpe mandaría cambios de la otra bandeja que quizá no dio
1034
+ // por terminados.
1035
+ if (currentTray === TRAYS.ASSETS) {
1036
+ handleOnSaveAssets();
1037
+ return;
1038
+ }
850
1039
  updateImages(token);
851
1040
  break;
852
1041
  default:
@@ -2269,7 +2458,11 @@ const ProviderProductEditionView = ({
2269
2458
  updatedImages={state.updated_images_values}
2270
2459
  images={state.services.images}
2271
2460
  // actions
2272
- downloadImages={handleOnDownloadImages}
2461
+ downloadImages={
2462
+ currentTray === TRAYS.ASSETS
2463
+ ? handleOnDownloadAssets
2464
+ : handleOnDownloadImages
2465
+ }
2273
2466
  askToDeleteImages={handleOnAskToDeleteImages}
2274
2467
  showSaveButton={canShowSaveButton(state.product.version_status)}
2275
2468
  onClickSave={handleOnClickSave}
@@ -2395,17 +2588,44 @@ const ProviderProductEditionView = ({
2395
2588
  {state.active_tab === "Imágenes" &&
2396
2589
  (state.product?.images_status !== "NS" ? (
2397
2590
  <>
2398
- {!imageLayout && (
2591
+ {enableDigitalAssets && (
2592
+ <GalleryTrayTabs
2593
+ activeTray={currentTray}
2594
+ setActiveTray={setActiveTray}
2595
+ imagesCount={
2596
+ state.images_values?.values?.length ?? 0
2597
+ }
2598
+ assetsCount={digitalAssets.assets.length}
2599
+ />
2600
+ )}
2601
+ {currentTray === TRAYS.ASSETS && (
2602
+ <section
2603
+ className="container"
2604
+ style={{ position: "relative" }}
2605
+ >
2606
+ <DigitalAssetsTray
2607
+ {...digitalAssets}
2608
+ unsaved={countUnsavedAssets(state)}
2609
+ gridLayout={imageLayout}
2610
+ readOnly={false}
2611
+ selectedAssets={state.selected_assets}
2612
+ setSelectedAssets={handleOnSetSelectedAssets}
2613
+ setCheckAll={handleOnSetAllAssetsChecked}
2614
+ />
2615
+ </section>
2616
+ )}
2617
+ {currentTray === TRAYS.IMAGES && !imageLayout && (
2399
2618
  <GalleryHeader
2400
2619
  checkAll={state.all_image_checked} // Determina si el checkbox "Seleccionar todo" está marcado
2401
2620
  setCheckAll={handleOnSetCheckAll} // Toggler de true o false para el checkbox "Seleccionar todo"
2402
2621
  setSelectedImages={handleOnSetSelectedImages}
2403
2622
  />
2404
2623
  )}
2405
- <section
2406
- className="container"
2407
- style={{ position: "relative" }}
2408
- >
2624
+ {currentTray === TRAYS.IMAGES && (
2625
+ <section
2626
+ className="container"
2627
+ style={{ position: "relative" }}
2628
+ >
2409
2629
  <div
2410
2630
  {...getRootProps({
2411
2631
  className: `dropzone ${
@@ -2467,6 +2687,7 @@ const ProviderProductEditionView = ({
2467
2687
  </div>
2468
2688
  )}
2469
2689
  </section>
2690
+ )}
2470
2691
  </>
2471
2692
  ) : (
2472
2693
  <ScreenHeader
package/src/index.js CHANGED
@@ -115,6 +115,7 @@ export * from "./components/pages/OnboardPlan";
115
115
  export * from "./components/pages/RegistrationLoginSecondStep";
116
116
  export * from "./components/pages/RegistrationLoginThirdStep";
117
117
  export * from "./components/pages/RetailerProductEdition";
118
+ export * from "./components/pages/CreatorsProductEdition";
118
119
  export * from "./components/pages/ProviderProductEdition";
119
120
  export * from "./components/pages/VerificationCodeResetPassword";
120
121
  export * from "./components/pages/MultipleEdition";