diff --git a/src/components/forms/form-template-form.js b/src/components/forms/form-template-form.js
deleted file mode 100644
index 3123cdd09..000000000
--- a/src/components/forms/form-template-form.js
+++ /dev/null
@@ -1,282 +0,0 @@
-/**
- * Copyright 2024 OpenStack Foundation
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * */
-
-import React, { useState, useEffect, useRef } from "react";
-import T from "i18n-react/dist/i18n-react";
-import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css";
-import {
- Input,
- UploadInputV2
-} from "openstack-uicore-foundation/lib/components";
-import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3";
-import Swal from "sweetalert2";
-import FormRepeater from "../form-repeater";
-import FormTemplateMetaFieldForm from "./form-template-meta-field-form";
-import { scrollToError, shallowEqual, hasErrors } from "../../utils/methods";
-import {
- MAX_FORM_TEMPLATE_MATERIALS_UPLOAD_SIZE,
- MAX_FORM_TEMPLATE_MATERIALS_UPLOAD_QTY,
- ALLOWED_FORM_TEMPLATE_MATERIAL_FORMATS
-} from "../../utils/constants";
-
-const FormTemplateForm = ({
- entity: initialEntity,
- errors: initialErrors,
- onMetaFieldTypeDeleted,
- onMetaFieldTypeValueDeleted,
- onMaterialDeleted,
- onSubmit
-}) => {
- const repeaterRef = useRef(null);
- const [entity, setEntity] = useState({ ...initialEntity });
- const [errors, setErrors] = useState(initialErrors);
-
- const mediaType = {
- max_size: MAX_FORM_TEMPLATE_MATERIALS_UPLOAD_SIZE,
- max_uploads_qty: MAX_FORM_TEMPLATE_MATERIALS_UPLOAD_QTY,
- type: {
- allowed_extensions: ALLOWED_FORM_TEMPLATE_MATERIAL_FORMATS
- }
- };
-
- useEffect(() => {
- scrollToError(initialErrors);
- if (!shallowEqual(initialEntity, entity)) {
- setEntity({ ...initialEntity });
- setErrors({});
- }
-
- if (!shallowEqual(initialErrors, errors)) {
- setErrors({ ...initialErrors });
- }
- }, [initialEntity, initialErrors]);
-
- const handleChange = (ev) => {
- const { id, value, checked, type } = ev.target;
- setEntity((prevEntity) => ({
- ...prevEntity,
- meta_fields: getNormalizedMetaFields(),
- [id]: type === "checkbox" ? checked : value
- }));
- setErrors((prevErrors) => ({ ...prevErrors, [id]: "" }));
- };
-
- const handleMaterialUploadComplete = (response) => {
- if (response) {
- const material = {
- file_path: `${response.path}${response.name}`,
- filename: response.name
- };
- setEntity((prevEntity) => ({
- ...prevEntity,
- meta_fields: getNormalizedMetaFields(),
- materials: [...prevEntity.materials, material]
- }));
- }
- };
-
- const handleRemoveMaterial = (materialFile) => {
- const materials = entity.materials.filter(
- (material) => material.filename != materialFile.name
- );
- setEntity((prevEntity) => ({
- ...prevEntity,
- meta_fields: getNormalizedMetaFields(),
- materials
- }));
-
- if (onMaterialDeleted && entity.id && materialFile.id) {
- onMaterialDeleted(entity.id, materialFile.id);
- }
- };
-
- const getMediaInputValue = () =>
- entity.materials.length > 0
- ? entity.materials.map((material) => ({
- ...material,
- filename: material.filename ?? material.file_path ?? material.file_url
- }))
- : [];
-
- const handleSubmit = (ev) => {
- ev.preventDefault();
- entity.meta_fields = getNormalizedMetaFields();
- onSubmit(entity);
- };
-
- const getNormalizedMetaFields = () => {
- if (repeaterRef.current) {
- const content = repeaterRef.current.getContent();
-
- return content.map((item) => {
- const idSuffix = item.id;
- const newValue = Object.fromEntries(
- Object.entries(item.value).map(([key, value]) => {
- const newKey = key.replace(`_${idSuffix}`, "");
- return [newKey, value];
- })
- );
- return newValue;
- });
- }
- return [];
- };
-
- const initMetaFieldLines = (metaFields) => [
- ...metaFields
- .filter((metaField) => metaField.id)
- .sort((a, b) => a.id - b.id)
- .map((metaField) => ({
- id: metaField.id,
- value: { ...metaField }
- })),
- ...metaFields
- .filter((metaField) => !metaField.id)
- .map((metaField) => ({
- id: Date.now(),
- value: { ...metaField }
- }))
- ];
-
- const handleRemoveMetaFieldType = async (metaField) => {
- if (!onMetaFieldTypeDeleted || !metaField.value.id) {
- return true;
- }
- const result = await Swal.fire({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("edit_form_template.delete_meta_field_warning")} ${
- metaField.value.id
- }`,
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
- if (result.value) {
- onMetaFieldTypeDeleted(entity.id, metaField.value.id);
- return true;
- }
- return false;
- };
-
- const handleRemoveMetaFieldTypeValue = (metaFieldId, metaFieldValueId) => {
- if (onMetaFieldTypeDeleted) {
- onMetaFieldTypeValueDeleted(entity.id, metaFieldId, metaFieldValueId);
- }
- };
-
- const renderMetaFieldForm = (line, updateValue) => (
-
- );
-
- return (
-
- );
-};
-
-export default FormTemplateForm;
diff --git a/src/components/forms/inventory-item-form.js b/src/components/forms/inventory-item-form.js
deleted file mode 100644
index 6b9b7ed4c..000000000
--- a/src/components/forms/inventory-item-form.js
+++ /dev/null
@@ -1,358 +0,0 @@
-/**
- * Copyright 2024 OpenStack Foundation
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * */
-
-import React, { useState, useEffect, useRef } from "react";
-import T from "i18n-react/dist/i18n-react";
-import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css";
-import {
- Input,
- UploadInputV2
-} from "openstack-uicore-foundation/lib/components";
-import TextEditorV3 from "openstack-uicore-foundation/lib/components/inputs/editor-input-v3";
-import Swal from "sweetalert2";
-import FormRepeater from "../form-repeater";
-import InventoryItemMetaFieldForm from "./inventory-item-meta-field-form";
-import { scrollToError, shallowEqual, hasErrors } from "../../utils/methods";
-import {
- MAX_INVENTORY_IMAGE_UPLOAD_SIZE,
- MAX_INVENTORY_IMAGES_UPLOAD_QTY,
- ALLOWED_INVENTORY_IMAGE_FORMATS
-} from "../../utils/constants";
-
-const InventoryItemForm = ({
- entity: initialEntity,
- errors: initialErrors,
- onMetaFieldTypeDeleted,
- onMetaFieldTypeValueDeleted,
- onImageDeleted,
- onSubmit
-}) => {
- const repeaterRef = useRef(null);
- const [entity, setEntity] = useState({ ...initialEntity });
- const [errors, setErrors] = useState(initialErrors);
-
- const mediaType = {
- max_size: MAX_INVENTORY_IMAGE_UPLOAD_SIZE,
- max_uploads_qty: MAX_INVENTORY_IMAGES_UPLOAD_QTY,
- type: {
- allowed_extensions: ALLOWED_INVENTORY_IMAGE_FORMATS
- }
- };
-
- useEffect(() => {
- scrollToError(initialErrors);
- if (!shallowEqual(initialEntity, entity)) {
- setEntity({ ...initialEntity });
- setErrors({});
- }
-
- if (!shallowEqual(initialErrors, errors)) {
- setErrors({ ...initialErrors });
- }
- }, [initialEntity, initialErrors]);
-
- const handleChange = (ev) => {
- const { id, value, checked, type } = ev.target;
- setEntity((prevEntity) => ({
- ...prevEntity,
- meta_fields: getNormalizedMetaFields(),
- [id]: type === "checkbox" ? checked : value
- }));
- setErrors((prevErrors) => ({ ...prevErrors, [id]: "" }));
- };
-
- const handleImageUploadComplete = (response) => {
- if (response) {
- const image = {
- file_path: `${response.path}${response.name}`,
- filename: response.name
- };
- setEntity((prevEntity) => ({
- ...prevEntity,
- meta_fields: getNormalizedMetaFields(),
- images: [...prevEntity.images, image]
- }));
- }
- };
-
- const handleRemoveImage = (imageFile) => {
- const images = entity.images.filter(
- (image) => image.filename != imageFile.name
- );
- setEntity((prevEntity) => ({
- ...prevEntity,
- meta_fields: getNormalizedMetaFields(),
- images
- }));
-
- if (onImageDeleted && entity.id && imageFile.id) {
- onImageDeleted(entity.id, imageFile.id);
- }
- };
-
- const getMediaInputValue = () =>
- entity.images.length > 0
- ? entity.images.map((img) => ({
- ...img,
- filename: img.filename ?? img.file_path ?? img.file_url
- }))
- : [];
-
- const handleSubmit = (ev) => {
- ev.preventDefault();
- entity.meta_fields = getNormalizedMetaFields();
- onSubmit(entity);
- };
-
- const getNormalizedMetaFields = () => {
- if (repeaterRef.current) {
- const content = repeaterRef.current.getContent();
-
- return content.map((item) => {
- const idSuffix = item.id;
- const newValue = Object.fromEntries(
- Object.entries(item.value).map(([key, value]) => {
- const newKey = key.replace(`_${idSuffix}`, "");
- return [newKey, value];
- })
- );
- return newValue;
- });
- }
- return [];
- };
-
- const initMetaFieldLines = (metaFields) => [
- ...metaFields
- .filter((metaField) => metaField.id)
- .sort((a, b) => a.id - b.id)
- .map((metaField) => ({
- id: metaField.id,
- value: { ...metaField }
- })),
- ...metaFields
- .filter((metaField) => !metaField.id)
- .map((metaField) => ({
- id: Date.now(),
- value: { ...metaField }
- }))
- ];
-
- const handleRemoveMetaFieldType = async (metaField) => {
- if (!onMetaFieldTypeDeleted || !metaField.value.id) {
- return true;
- }
- const result = await Swal.fire({
- title: T.translate("general.are_you_sure"),
- text: `${T.translate("edit_inventory_item.delete_meta_field_warning")} ${
- metaField.value.id
- }`,
- type: "warning",
- showCancelButton: true,
- confirmButtonColor: "#DD6B55",
- confirmButtonText: T.translate("general.yes_delete")
- });
- if (result.value) {
- onMetaFieldTypeDeleted(entity.id, metaField.value.id);
- return true;
- }
- return false;
- };
-
- const handleRemoveMetaFieldTypeValue = (metaFieldId, metaFieldValueId) => {
- if (onMetaFieldTypeDeleted) {
- onMetaFieldTypeValueDeleted(entity.id, metaFieldId, metaFieldValueId);
- }
- };
-
- const renderMetaFieldForm = (line, updateValue) => (
-
- );
-
- return (
-
- );
-};
-
-export default InventoryItemForm;
diff --git a/src/layouts/page-template-layout.js b/src/layouts/page-template-layout.js
index 445381daa..37eba71bb 100644
--- a/src/layouts/page-template-layout.js
+++ b/src/layouts/page-template-layout.js
@@ -17,7 +17,6 @@ import T from "i18n-react/dist/i18n-react";
import { Breadcrumb } from "react-breadcrumbs";
import Restrict from "../routes/restrict";
import NoMatchPage from "../pages/no-match-page";
-import EditPageTemplatePage from "../pages/sponsors-global/page-templates/edit-page-template-page";
import PageTemplateListPage from "../pages/sponsors-global/page-templates/page-template-list-page";
const PageTemplateLayout = ({ match }) => (
@@ -29,18 +28,6 @@ const PageTemplateLayout = ({ match }) => (
}}
/>
-
-
)}
{showInventoryItemModal && (
-
- initialEntity.images?.length > 0
- ? initialEntity.images.map((img) => {
- const filename = img.filename ?? img.file_path ?? img.file_url;
- return {
- ...img,
- filename: filename.concat("?t=", Date?.now())
- };
- })
- : [];
-
const handleClose = () => {
formik.resetForm();
onClose();
@@ -283,7 +273,7 @@ const SponsorItemDialog = ({
id="image-upload"
name="image"
onUploadComplete={handleImageUploadComplete}
- value={getMediaInputValue()}
+ value={getMediaInputValue(initialEntity)}
mediaType={mediaType}
onRemove={handleRemoveImage}
postUrl={`${window.FILE_UPLOAD_API_BASE_URL}/api/v1/files/upload`}
diff --git a/src/pages/sponsors-global/page-templates/edit-page-template-page.js b/src/pages/sponsors-global/page-templates/edit-page-template-page.js
deleted file mode 100644
index cc3c1d25e..000000000
--- a/src/pages/sponsors-global/page-templates/edit-page-template-page.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * Copyright 2024 OpenStack Foundation
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * */
-
-import React, { useEffect } from "react";
-import { connect } from "react-redux";
-import { Breadcrumb } from "react-breadcrumbs";
-import T from "i18n-react/dist/i18n-react";
-import FormTemplateForm from "../../../components/forms/form-template-form";
-import {
- getFormTemplate,
- resetFormTemplateForm,
- saveFormTemplate,
- deleteFormTemplateMetaFieldType,
- deleteFormTemplateMetaFieldTypeValue,
- deleteFormTemplateMaterial
-} from "../../../actions/form-template-actions";
-
-const EditPageTemplatePage = (props) => {
- const {
- match,
- entity,
- errors,
- getFormTemplate,
- resetFormTemplateForm,
- saveFormTemplate,
- deleteFormTemplateMetaFieldType,
- deleteFormTemplateMetaFieldTypeValue,
- deleteFormTemplateMaterial
- } = props;
- const pageTemplateId = match.params.page_template_id;
-
- useEffect(() => {
- if (!pageTemplateId) {
- resetFormTemplateForm();
- } else {
- getFormTemplate(pageTemplateId);
- }
- }, [pageTemplateId, getFormTemplate, resetFormTemplateForm]);
-
- const title = entity.id
- ? T.translate("general.edit")
- : T.translate("general.add");
- const breadcrumb = entity.id ? entity.name : T.translate("general.new");
-
- return (
-
-
-
- {title} {T.translate("edit_form_template.form_template")}
-
-
-
-
- );
-};
-
-const mapStateToProps = ({ currentFormTemplateState }) => ({
- ...currentFormTemplateState
-});
-
-export default connect(mapStateToProps, {
- getFormTemplate,
- resetFormTemplateForm,
- saveFormTemplate,
- deleteFormTemplateMetaFieldType,
- deleteFormTemplateMetaFieldTypeValue,
- deleteFormTemplateMaterial
-})(EditPageTemplatePage);
diff --git a/src/utils/__tests__/methods.test.js b/src/utils/__tests__/methods.test.js
new file mode 100644
index 000000000..4f964d34f
--- /dev/null
+++ b/src/utils/__tests__/methods.test.js
@@ -0,0 +1,58 @@
+import { getMediaInputValue } from "../methods";
+
+const FIXED_NOW = 1_772_551_911_231;
+beforeAll(() => jest.spyOn(Date, "now").mockReturnValue(FIXED_NOW));
+afterAll(() => jest.restoreAllMocks());
+
+describe("getMediaInputValue", () => {
+ describe("fileUrl guard — all url fields undefined/null", () => {
+ it("should does NOT throw TypeError when all url fields are undefined", () => {
+ expect(() => getMediaInputValue({ images: [{ id: 1 }] })).not.toThrow();
+ });
+
+ it("should returns filename: '' when all url fields are null", () => {
+ const [result] = getMediaInputValue({
+ images: [{ filename: null, file_path: null, file_url: null }]
+ });
+ expect(result.filename).toBe("");
+ });
+
+ it("should preserves other props on the image object when filename is empty", () => {
+ const [result] = getMediaInputValue({
+ images: [{ id: 42, alt: "broken" }]
+ });
+ expect(result).toMatchObject({ id: 42, alt: "broken", filename: "" });
+ });
+ });
+
+ describe("path stripping", () => {
+ it("should strips the directory prefix and keeps only the basename", () => {
+ const [result] = getMediaInputValue({
+ images: [{ filename: "uploads/2024/photo.jpg" }]
+ });
+ expect(result.filename).toBe("photo.jpg");
+ });
+
+ it("should keeps the filename unchanged when there is no slash", () => {
+ const [result] = getMediaInputValue({
+ images: [{ filename: "photo.jpg" }]
+ });
+ expect(result.filename).toBe("photo.jpg");
+ });
+ });
+
+ describe("files without extensions", () => {
+ it("should returns 'README' as filename", () => {
+ const [result] = getMediaInputValue({ images: [{ filename: "README" }] });
+ expect(result.filename).toBe("README");
+ expect(result.filename.startsWith(".")).toBe(false);
+ });
+
+ it("should strips the path for an extension-less file in a subdirectory", () => {
+ const [result] = getMediaInputValue({
+ images: [{ filename: "some/path/README" }]
+ });
+ expect(result.filename).toBe("README");
+ });
+ });
+});
diff --git a/src/utils/constants.js b/src/utils/constants.js
index c6b095009..2e432af48 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -155,9 +155,13 @@ export const EVENT_TYPE_FISHBOWL = "Fishbowl";
export const EVENT_TYPE_GROUP_EVENTS = "Groups Events";
+export const FILENAME_TRUNCATE_SIDE_PERCENT = 0.4;
+
+export const TRIM_TEXT_LENGTH_20 = 20;
+
export const TRIM_TEXT_LENGTH_50 = 50;
-export const TRIM_TEXT_LENGTH_40 = 50;
+export const TRIM_TEXT_LENGTH_40 = 40;
export const LANGUAGE_CODE_LENGTH = 2;
diff --git a/src/utils/methods.js b/src/utils/methods.js
index a73060818..a9ab9fce5 100644
--- a/src/utils/methods.js
+++ b/src/utils/methods.js
@@ -18,6 +18,7 @@ import {
initLogOut
} from "openstack-uicore-foundation/lib/security/methods";
import Swal from "sweetalert2";
+import URI from "urijs";
import * as Sentry from "@sentry/react";
import T from "i18n-react/dist/i18n-react";
import {
@@ -532,5 +533,25 @@ export const formatBadgeQR = (code, summit) => {
return null;
};
+export const getMediaInputValue = (entity, fieldName = "images") => {
+ const mediaFiles = entity?.[fieldName];
+ if (!mediaFiles?.length) return [];
+
+ return mediaFiles.map((img) => {
+ const fileUrl = img.filename ?? img.file_path ?? img.file_url;
+ if (!fileUrl) return { ...img, filename: "" };
+
+ const fileName = new URI(fileUrl).filename();
+ const publicURL = new URI(img?.public_url || fileUrl)
+ .setQuery("t", Date.now())
+ .toString();
+
+ return {
+ ...img,
+ public_url: publicURL,
+ filename: fileName
+ };
+ });
+};
// eslint-disable-next-line no-magic-numbers
export const bytesToMb = (bytes) => (bytes / BYTES_IN_MEGABYTE).toFixed(2);