Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8e77f34
2927: Started work on upgrading poster component to work with v2 api
tuj Nov 22, 2024
0e9f0e6
Merge branch 'develop' into feature/eventdatabasen-v2
tuj Jan 23, 2025
4aae53d
2927: Created v2 of poster selector
tuj Jan 27, 2025
8b041a9
2927: Code cleanup
tuj Jan 27, 2025
b10f6d4
2927: Started work on fixing data flow
tuj Jan 28, 2025
829e8fc
2927: Fixed single
tuj Jan 28, 2025
4bab571
2927: Fixes to subscription
tuj Jan 28, 2025
46c3cfa
2927: Added debounce to options
tuj Jan 29, 2025
58f6e72
2927: Use options endpoint
tuj Jan 31, 2025
d616446
2927: Fixed options endpoint
tuj Feb 1, 2025
d1b738c
2927: Fixed reload of options
tuj Feb 1, 2025
72bc6e3
2927: Fixed multiple occurrences case
tuj Feb 3, 2025
57fdd17
2927: Fixed linting issues
tuj Feb 3, 2025
e2dfc98
2927: Changed to upload artifact v4
tuj Feb 3, 2025
a703581
2927: Updated changelog
tuj Feb 3, 2025
cf83ea9
2927: Fixed warning
tuj Feb 3, 2025
b962b64
2927: Fixed occurences issue
tuj Feb 3, 2025
0c19ab2
2927: Styling
tuj Feb 3, 2025
d9cec03
2927: Fixed styling and texts
tuj Feb 3, 2025
30cac60
2927: Applied coding standards
tuj Feb 3, 2025
02e15a6
2927: Fixed issues raised in code review
tuj Feb 4, 2025
8dc65b0
2927: Deconstruct props
tuj Feb 4, 2025
65a6eed
2927: Fixed bugs introduced by deconstruction
tuj Feb 4, 2025
36fc7ba
2927: Moved options to helper
tuj Feb 4, 2025
05f9ce3
2927: Moved overrides to own component
tuj Feb 5, 2025
bf3214f
2927: Refactored poster single component
tuj Feb 5, 2025
195cbe9
2927: Split poster single up into components
tuj Feb 5, 2025
fabfbd4
2927: Applied coding standards
tuj Feb 5, 2025
aa91a52
2927: Removed url from search options
tuj Feb 5, 2025
40e3d1f
2927: Added placeholder text
tuj Feb 5, 2025
da98eaf
2927: Refactored poster subscription
tuj Feb 5, 2025
53e8abd
2927: Added alert with information
tuj Feb 5, 2025
3874ef9
2927: Added placeholders
tuj Feb 5, 2025
ac12491
Merge branch 'develop' into feature/eventdatabasen-v2
tuj Mar 18, 2025
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
3 changes: 1 addition & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ jobs:
docker compose run --rm playwright npx playwright install --with-deps
docker compose run --rm playwright npx playwright test --retries 3


- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- [#271](https://github.com/os2display/display-admin-client/pull/271)
- Added new feed source: Eventdatabasen v2.
- [#273](https://github.com/os2display/display-admin-client/pull/273)
- Fixed calendar api feed source config endpoint.
- [#272](https://github.com/os2display/display-admin-client/pull/272)
Expand Down
8 changes: 8 additions & 0 deletions src/components/feed-sources/feed-source-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import FormInput from "../util/forms/form-input";
import CalendarApiFeedType from "./templates/calendar-api-feed-type";
import NotifiedFeedType from "./templates/notified-feed-type";
import EventDatabaseApiFeedType from "./templates/event-database-feed-type";
import EventDatabaseApiV2FeedType from "./templates/event-database-v2-feed-type";

/**
* The feed-source form component.
Expand Down Expand Up @@ -97,6 +98,13 @@ function FeedSourceForm({
mode={mode}
/>
)}
{feedSource?.feedType === "App\\Feed\\EventDatabaseApiV2FeedType" && (
<EventDatabaseApiV2FeedType
handleInput={handleSecretInput}
formStateObject={feedSource.secrets}
mode={mode}
/>
)}
{feedSource?.feedType === "App\\Feed\\NotifiedFeedType" && (
<NotifiedFeedType
handleInput={handleSecretInput}
Expand Down
9 changes: 9 additions & 0 deletions src/components/feed-sources/feed-source-manager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ function FeedSourceManager({
host: "",
},
},
{
value: "App\\Feed\\EventDatabaseApiV2FeedType",
title: t("event-database-api-v2-feed-type.title"),
key: "7",
secretsDefault: {
host: "",
apikey: "",
},
},
{
value: "App\\Feed\\NotifiedFeedType",
title: t("dynamic-fields.notified-feed-type.title"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { React } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import FormInput from "../../util/forms/form-input";

const EventDatabaseApiV2FeedType = ({ handleInput, formStateObject, mode }) => {
const { t } = useTranslation("common", {
keyPrefix: "event-database-api-v2-feed-type",
});
return (
<>
<FormInput
name="host"
type="text"
className="mb-2"
label={t("host")}
onChange={handleInput}
value={formStateObject?.host}
/>
<FormInput
name="apikey"
type="text"
label={t("apikey")}
onChange={handleInput}
placeholder={
mode === "PUT" ? t("redacted-value-input-placeholder") : ""
}
value={formStateObject?.apikey}
/>
</>
);
};

EventDatabaseApiV2FeedType.propTypes = {
handleInput: PropTypes.func,
formStateObject: PropTypes.shape({
host: PropTypes.string.isRequired,
apikey: PropTypes.string,
}),
mode: PropTypes.string,
};

export default EventDatabaseApiV2FeedType;
28 changes: 25 additions & 3 deletions src/components/slide/content/feed-selector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import MultiSelectComponent from "../../util/forms/multiselect-dropdown/multi-dr
import idFromUrl from "../../util/helpers/id-from-url";
import ContentForm from "./content-form";
import MultiselectFromEndpoint from "./multiselect-from-endpoint";
import PosterSelector from "./poster-selector";
import PosterSelector from "./poster/poster-selector";
import PosterSelectorV2 from "./poster/poster-selector-v2";

/**
* Feed selector.
Expand Down Expand Up @@ -96,9 +97,19 @@ function FeedSelector({
onChange(newValue);
};

const configurationChange = ({ target }) => {
const configurationChange = ({ target = null, targets = null }) => {
const configuration = { ...value.configuration };
set(configuration, target.id, target.value);

if (target !== null) {
set(configuration, target.id, target.value);
}

if (targets !== null) {
for (let i = 0; i < targets.length; i += 1) {
set(configuration, targets[i].id, targets[i].value);
}
}

const newValue = { ...value, configuration };
onChange(newValue);
};
Expand Down Expand Up @@ -137,6 +148,17 @@ function FeedSelector({
/>
);
}
if (element?.input === "poster-selector-v2") {
return (
<PosterSelectorV2
key={element.key}
feedSource={feedSourceData}
configurationChange={configurationChange}
configuration={value.configuration}
getValueFromConfiguration={getValueFromConfiguration}
/>
);
}

return (
<ContentForm
Expand Down
89 changes: 89 additions & 0 deletions src/components/slide/content/poster/poster-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import dayjs from "dayjs";
import localeDa from "dayjs/locale/da";
import localStorageKeys from "../../../util/local-storage-keys";

const capitalize = (s) => {
return s.charAt(0).toUpperCase() + s.slice(1);
};

const formatDate = (date, format) => {
if (!date) return "";
return capitalize(
dayjs(date)
.locale(localeDa)
.format(format ?? "LLLL")
);
};

const loadDropdownOptions = (url, headers, inputValue, callback, type) => {
const params = {
type,
display: "options",
};

if (inputValue) {
params.name = inputValue;
}

const query = new URLSearchParams(params);

fetch(`${url}?${query}`, {
headers,
})
.then((response) => response.json())
.then((data) => {
callback(data);
})
.catch(() => {
callback([]);
});
};

const loadDropdownOptionsPromise = (url, headers, inputValue, type) => {
return new Promise((resolve, reject) => {
const params = {
entityType: type,
};

if (inputValue) {
params.search = inputValue;
}

const query = new URLSearchParams(params);
fetch(`${url}?${query}`, {
headers,
})
.then((response) => response.json())
.then((data) => {
resolve(data);
})
.catch((reason) => {
reject(reason);
});
});
};

const getHeaders = () => {
const apiToken = localStorage.getItem(localStorageKeys.API_TOKEN);
const tenantKey = JSON.parse(
localStorage.getItem(localStorageKeys.SELECTED_TENANT)
);

const headers = {
authorization: `Bearer ${apiToken ?? ""}`,
};

if (tenantKey) {
headers["Authorization-Tenant-Key"] = tenantKey.tenantKey;
}

return headers;
};

export {
formatDate,
capitalize,
loadDropdownOptions,
getHeaders,
loadDropdownOptionsPromise,
};
104 changes: 104 additions & 0 deletions src/components/slide/content/poster/poster-selector-v2.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { React } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import { Button, Card, Row } from "react-bootstrap";
import Col from "react-bootstrap/Col";
import PosterSingle from "./poster-single";
import PosterSubscription from "./poster-subscription";

/**
* @param {object} props Props.
* @param {object} props.feedSource Feed source.
* @param {Function} props.getValueFromConfiguration Gets a value from the feed
* configuration.
* @param {Function} props.configurationChange Configuration onChange.
* @param {object} props.configuration Configuration.
* @returns {object} PosterSelector component.
*/
function PosterSelectorV2({
feedSource,
getValueFromConfiguration,
configuration,
configurationChange,
}) {
const { t } = useTranslation("common", { keyPrefix: "poster-selector-v2" });
const posterType = getValueFromConfiguration("posterType");

return (
<Card className="mb-3">
<Card.Body>
{!posterType && (
<Row>
<Col>
<h5>{t("select-mode")}</h5>
</Col>
<Col>
<Button
onClick={() =>
configurationChange({
target: {
id: "posterType",
value: "single",
},
})
}
>
{t("poster-feed-type-single")}
</Button>
</Col>
<Col>
<Button
onClick={() =>
configurationChange({
target: {
id: "posterType",
value: "subscription",
},
})
}
>
{t("poster-feed-type-subscription")}
</Button>
</Col>
</Row>
)}
{posterType && (
<>
{posterType === "single" && (
<PosterSingle
feedSource={feedSource}
configuration={configuration}
getValueFromConfiguration={getValueFromConfiguration}
configurationChange={configurationChange}
/>
)}
{posterType === "subscription" && (
<PosterSubscription
feedSource={feedSource}
configuration={configuration}
getValueFromConfiguration={getValueFromConfiguration}
configurationChange={configurationChange}
/>
)}
</>
)}
</Card.Body>
</Card>
);
}

PosterSelectorV2.propTypes = {
getValueFromConfiguration: PropTypes.func.isRequired,
configurationChange: PropTypes.func.isRequired,
configuration: PropTypes.shape({}),
feedSource: PropTypes.shape({
admin: PropTypes.arrayOf(
PropTypes.shape({
endpointEntity: PropTypes.string,
endpointSearch: PropTypes.string,
})
),
}).isRequired,
};

export default PosterSelectorV2;
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import AsyncSelect from "react-select/async";
import Col from "react-bootstrap/Col";
import dayjs from "dayjs";
import localeDa from "dayjs/locale/da";
import Select from "../../util/forms/select";
import FormInput from "../../util/forms/form-input";
import FormCheckbox from "../../util/forms/form-checkbox";
import localStorageKeys from "../../util/local-storage-keys";
import Select from "../../../util/forms/select";
import FormInput from "../../../util/forms/form-input";
import FormCheckbox from "../../../util/forms/form-checkbox";
import localStorageKeys from "../../../util/local-storage-keys";

/**
* @param {object} props Props.
Expand Down Expand Up @@ -650,7 +650,7 @@ function PosterSelector({
<th scope="col">
{t("poster-selector.table-price")}
</th>
<th scope="col"> </th>
<th scope="col" />
</tr>
</thead>
<tbody>
Expand Down
Loading