Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- Upgraded to Vite 8 and React 19.

## [3.0.0-rc3] - 2026-05-11

- Made the Admin login sidebar text configurable via the new `ADMIN_LOGIN_SCREEN_TEXT`
Expand Down
1 change: 1 addition & 0 deletions assets/admin/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ function App() {

useEffect(() => {
i18next.init({
showSupportNotice: false,
interpolation: { escapeValue: false }, // React already does escaping
lng: "da", // language to use
keySeparator: ".",
Expand Down
4 changes: 2 additions & 2 deletions assets/admin/components/media/image-list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ function ImageList({ media = [], multiple }) {
/>
<Form.Check
type="checkbox"
onClick={() => selectImage(data)}
checked={selected.find((item) => item.id === data["@id"])}
onChange={() => selectImage(data)}
checked={!!selected.find((item) => item.id === data["@id"])}
aria-label={t("checkbox-form-aria-label", { this: data.title })}
/>
<div className="card-body">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function TenantSelector() {
return (
<Nav.Item className="text-white mb-2 mt-4">
<div className="text-white">{t("tenant")}</div>
<div className="text-white fw-bold">
<div className="text-white fw-bold me-3">
{tenants && (
<>
{tenants.length === 1 && (
Expand Down
3 changes: 2 additions & 1 deletion assets/admin/components/screen/screen-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function ScreenForm({
orientationOptions,
resolutionOptions,
groupId = "",
screenId = null,
isLoading = false,
loadingMessage = "",
screen = null,
Expand Down Expand Up @@ -195,7 +196,7 @@ function ScreenForm({
<SelectGroupsTable
handleChange={handleInput}
name="inScreenGroups"
id={groupId}
id={screenId}
getSelectedMethod={
enhancedApi.endpoints.getV2ScreensByIdScreenGroups.initiate
}
Expand Down
1 change: 1 addition & 0 deletions assets/admin/components/screen/screen-manager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ function ScreenManager({
isLoading={savingScreen || isLoading}
loadingMessage={loadingMessage}
groupId={groupId}
screenId={id}
/>
)}
</>
Expand Down
105 changes: 105 additions & 0 deletions assets/admin/components/screen/util/campaign-icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { useTranslation } from "react-i18next";
import Spinner from "react-bootstrap/Spinner";
import idFromUrl from "../../util/helpers/id-from-url";
import calculateIsPublished from "../../util/helpers/calculate-is-published";
import {
enhancedApi,
useGetV2ScreensByIdCampaignsQuery,
useGetV2ScreensByIdScreenGroupsQuery,
} from "../../../../shared/redux/enhanced-api.ts";

/**
* An icon to show if the screen has an active campaign.
*
* @param {object} props - The props.
* @param {string} props.id The id of the screen.
* @param {number} props.delay Delay the fetch.
* @returns {object} The campaign icon.
*/
function CampaignIcon({ id, delay = 1000 }) {
const { t } = useTranslation("common", { keyPrefix: "campaign-icon" });
const dispatch = useDispatch();
const [isOverriddenByCampaign, setIsOverriddenByCampaign] = useState(null);
const [screenCampaignsChecked, setScreenCampaignsChecked] = useState(false);
const [allCampaigns, setAllCampaigns] = useState([]);
const [getData, setGetData] = useState(false);

const { data: campaigns, isLoading } = useGetV2ScreensByIdCampaignsQuery(
{ id },
{ skip: !getData || !id },
);
const { data: groups, isLoading: isLoadingScreenGroups } =
useGetV2ScreensByIdScreenGroupsQuery({ id }, { skip: !getData || !id });

useEffect(() => {
if (campaigns) {
setAllCampaigns(
campaigns["hydra:member"].map(({ campaign }) => campaign),
);
setScreenCampaignsChecked(true);
}
}, [campaigns]);

useEffect(() => {
if (groups && !isOverriddenByCampaign && screenCampaignsChecked) {
groups["hydra:member"].forEach((group) => {
dispatch(
enhancedApi.endpoints.getV2ScreenGroupsByIdCampaigns.initiate({
id: idFromUrl(group["@id"]),
}),
).then((result) => {
let allCampaignsCopy = [...allCampaigns];
if (allCampaignsCopy.length > 0 && result.data) {
allCampaignsCopy = allCampaignsCopy.concat(
result.data["hydra:member"].map(({ campaign }) => campaign),
);
}
setAllCampaigns(allCampaignsCopy);
});
});
}
}, [groups, screenCampaignsChecked]);

useEffect(() => {
if (allCampaigns.length > 0 && !isOverriddenByCampaign) {
allCampaigns.forEach(({ published }) => {
if (calculateIsPublished(published)) {
setIsOverriddenByCampaign(true);
}
});
}
}, [allCampaigns]);

useEffect(() => {
const timeout = setTimeout(() => {
setGetData(true);
}, delay);

return () => {
clearTimeout(timeout);
};
}, []);

if (!getData || isLoading || isLoadingScreenGroups) {
return (
<div style={{ height: "38px" }}>
<Spinner
as="span"
animation="border"
size="sm"
role="status"
aria-hidden="true"
className="m-1"
/>
</div>
);
}

return isOverriddenByCampaign
? t("overridden-by-campaign")
: t("not-overridden-by-campaign");
}

export default CampaignIcon;
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function ContactForm({
target: { id: `${name}.${index}.image`, value: target.value },
});
}}
acceptedMimetypes={["image/*"]}
acceptedMimetypes={{ "image/*": [] }}
multiple={false}
name="image"
/>
Expand Down
6 changes: 3 additions & 3 deletions assets/admin/components/slide/content/content-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function ContentForm({
*/
function renderElement(formData) {
let returnElement;
let defaultMimetypes = null;
let defaultMimetypes = {};
switch (formData.input) {
case "checkbox-options":
returnElement = (
Expand All @@ -64,9 +64,9 @@ function ContentForm({
case "video":
case "file":
if (formData.input === "image") {
defaultMimetypes = ["image/*"];
defaultMimetypes["image/*"] = [];
} else if (formData.input === "video") {
defaultMimetypes = ["video/*"];
defaultMimetypes["video/*"] = [];
}

returnElement = (
Expand Down
2 changes: 1 addition & 1 deletion assets/admin/components/slide/content/file-selector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function FileSelector({
const renderFileFormElements = (fileEntries) =>
fileEntries.map((fileEntry) => (
<div
key={fileEntry["@id"]}
key={fileEntry["@id"] || fileEntry.tempId}
className="bg-light border p-3 pb-0 rounded my-3"
>
<FileFormElement
Expand Down
8 changes: 7 additions & 1 deletion assets/admin/components/util/fetch-data-hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ function useFetchDataHook(apiCall, ids, params = {}, key = "id") {
const validIds = ids.filter((id) => id != null && id !== "");
if (validIds.length === 0) return;

// Check if params contain invalid values
const hasInvalidParams = Object.values(params).some(
(value) => value === "" || value == null,
);
if (hasInvalidParams) return;

async function fetchItems() {
setLoading(true);

try {
let allItems = [];
let fetchedItems = [];

for (const id of ids) {
for (const id of validIds) {
let page = 1;
let totalItems = 1; // Will be overridden when we know the total amount.

Expand Down
2 changes: 1 addition & 1 deletion assets/admin/components/util/forms/select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function Select({
required={isRequired}
id={name}
name={name}
value={value}
value={value ?? ""}
onChange={onChange}
disabled={disabled}
>
Expand Down
Loading