diff --git a/assets/js/App.js b/assets/js/App.js
index dd2f561ea50a..2e8b467436e3 100644
--- a/assets/js/App.js
+++ b/assets/js/App.js
@@ -2,6 +2,7 @@ import React, { useRef } from 'react';
import ReactDOM from 'react-dom';
import { SearchContainer } from './components/Search/SearchContainer.js';
import { CatalogueContainer } from './components/Catalogue/CatalogueContainer.js';
+import { PubliccodeBadge } from './components/PubliccodeBadge.js';
import { CatalogueItem } from './components/Catalogue/CatalogueItem.js';
import { MailingListSubscribe } from './components/MailingList/MailingListSubscribe.js';
import { MailingListConfirmation } from './components/MailingList/MailingListConfirmation.js';
@@ -9,6 +10,7 @@ import { MailingListConfirmation } from './components/MailingList/MailingListCon
export const App = () => {
const search = useRef(Array.from(document.getElementsByTagName('custom-search')));
const catalogue = useRef(Array.from(document.getElementsByTagName('custom-catalogue')));
+ const publiccodeBadge = useRef(Array.from(document.getElementsByTagName('publiccode-badge')));
const thumbnails = useRef(Array.from(document.getElementsByTagName('catalogue-item')));
const mailingListSubscribe = useRef(Array.from(document.getElementsByTagName('mailing-list-subscribe')));
const mailingListConfirmation = useRef(Array.from(document.getElementsByTagName('mailing-list-confirmation')));
@@ -32,6 +34,7 @@ export const App = () => {
{thumbnails.current.map((t) => renderThumbnail(t))}
{mailingListSubscribe.current.map((p) => renderCustomElement(p, MailingListSubscribe))}
{mailingListConfirmation.current.map((p) => renderCustomElement(p, MailingListConfirmation))}
+ {publiccodeBadge.current.map((p) => renderCustomElement(p, PubliccodeBadge))}
>
);
};
diff --git a/assets/js/components/PubliccodeBadge.js b/assets/js/components/PubliccodeBadge.js
new file mode 100644
index 000000000000..b3a22d8c9f9d
--- /dev/null
+++ b/assets/js/components/PubliccodeBadge.js
@@ -0,0 +1,59 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+
+import { Spinner } from './Spinner';
+
+const stateClass = {
+ loading: 'secondary',
+ good: 'success',
+ error: 'danger',
+};
+
+export const PubliccodeBadge = React.memo(({ id }) => {
+ const [publiccodeState, setPubliccodeState] = useState('loading');
+ const logUrl = `https://api.developers.italia.it/v1/software/${id}/logs`;
+
+ useEffect(() => {
+ async function fetchLogs() {
+ const res = await fetch(logUrl);
+
+ if (res.status >= 200 || res.status <= 299) {
+ const data = await res.json();
+ const good = data.data[0]?.message.includes('GOOD publiccode.yml');
+
+ setPubliccodeState(good ? 'good' : 'error');
+ } else {
+ setPubliccodeState('error');
+ }
+ }
+
+ fetchLogs();
+ }, [logUrl]);
+
+ return (
+ <>
+
+ {publiccodeState === 'loading' && }
+
+ {publiccodeState !== 'loading' && publiccodeState}
+
+
+
+
+ (log)
+
+ >
+ );
+});
+
+PubliccodeBadge.propTypes = {
+ id: PropTypes.string.isRequired,
+};
+
+PubliccodeBadge.displayName = 'PubliccodeBadge';
diff --git a/assets/js/components/Spinner.css b/assets/js/components/Spinner.css
new file mode 100644
index 000000000000..938da3d3515f
--- /dev/null
+++ b/assets/js/components/Spinner.css
@@ -0,0 +1,56 @@
+.lds-ellipsis {
+ display: inline-block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+ margin-bottom: 1rem;
+}
+.lds-ellipsis div {
+ position: absolute;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: gray;
+ top: 0.5rem;
+ animation-timing-function: cubic-bezier(0, 1, 1, 0);
+}
+.lds-ellipsis div:nth-child(1) {
+ left: 8px;
+ animation: lds-ellipsis1 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(2) {
+ left: 8px;
+ animation: lds-ellipsis2 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(3) {
+ left: 32px;
+ animation: lds-ellipsis2 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(4) {
+ left: 56px;
+ animation: lds-ellipsis3 0.6s infinite;
+}
+@keyframes lds-ellipsis1 {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+@keyframes lds-ellipsis3 {
+ 0% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
+@keyframes lds-ellipsis2 {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 100% {
+ transform: translate(24px, 0);
+ }
+}
diff --git a/assets/js/components/Spinner.js b/assets/js/components/Spinner.js
new file mode 100644
index 000000000000..3c31df1e44e1
--- /dev/null
+++ b/assets/js/components/Spinner.js
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import './Spinner.css';
+
+export const Spinner = React.memo(() => (
+ <>
+
+ >
+));
+
+Spinner.displayName = 'Spinner';