Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional terms of use dialog for user #825

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
5 changes: 5 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { availableHotkeys } from "../configs/hotkeysConfig";
import { studioURL } from "../configs/generalConfig";
import { hasAccess } from "../utils/utils";
import RegistrationModal from "./shared/RegistrationModal";
import TermsOfUseModal from "./shared/TermsOfUseModal";
import HotKeyCheatSheet from "./shared/HotKeyCheatSheet";
import { useHotkeys } from "react-hotkeys-hook";
import { useAppDispatch, useAppSelector } from "../store";
Expand Down Expand Up @@ -63,6 +64,7 @@ const Header = ({
const errorCounter = useAppSelector(state => getErrorCount(state));
const user = useAppSelector(state => getUserInformation(state));
const orgProperties = useAppSelector(state => getOrgProperties(state));
const displayTerms = (orgProperties['org.opencastproject.admin.display_terms'] || 'false').toLowerCase() === 'true';

const loadHealthStatus = async () => {
await dispatch(fetchHealthStatus());
Expand Down Expand Up @@ -284,6 +286,9 @@ const Header = ({
<RegistrationModal close={hideRegistrationModal} />
)}

{/* Terms of use for all non-admin users */}
{displayTerms && !user.roles.includes("ROLE_ADMIN") && <TermsOfUseModal />}

{/* Hotkey Cheat Sheet */}
{displayHotKeyCheatSheet && (
<HotKeyCheatSheet close={hideHotKeyCheatSheet} />
Expand Down
152 changes: 152 additions & 0 deletions src/components/shared/TermsOfUseModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Field, Formik } from "formik";
import cn from "classnames";
import axios from "axios";
import i18n from "../../i18n/i18n";
import DOMPurify from "dompurify";

// Generate URL for terms based on the languae
const getURL = (language: string) => {
return `/ui/config/admin-ui/terms.${language}.html`;
};

const TermsOfUseModal = () => {
const { t } = useTranslation();
const [termsContent, setTermsContent] = useState("");
const [agreedToTerms, setAgreedToTerms] = useState(true);

// Check if already accepted terms
useEffect(() => {
const checkTerms = async () => {
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
try {
const response = await axios.get("/admin-ng/user-settings/settings.json");
// @ts-expect-error TS(7006): Parameter 'result' implicitly has an 'any' type.
const isAgreed = response.data.results.find(result => result.key === "agreedToTerms").value === "true";
setAgreedToTerms(isAgreed);
} catch (error) {
console.error("Error while retrieving data: ", error);
setAgreedToTerms(false);
}
};

checkTerms();
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
}, []);

// Fetch terms
useEffect(() => {
axios.get(getURL(i18n.language))
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
.then(response => {
setTermsContent(response.data);
})
.catch(error => {
axios.get(getURL(typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : 'en-US'))
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite working right. I can see it requesting my region's terms (which correctly 404), and I can see it calling setTermsContent with the correct en-US terms. But the modal itself never pops up.

Copy link
Contributor Author

@schuettloeffel-elsa schuettloeffel-elsa Oct 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works fine for me, what user are you trying this with? ROLE_ADMIN will never see this, also the property org.opencastproject.admin.display_terms has to be true

.then(response => {
setTermsContent(response.data);
})
.catch(error => {
console.error('Error while fetching data:', error);
setTermsContent(t("TERMS.NOCONTENT"));
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [agreedToTerms]); // Listen to changes in agreedToTerms

// Set terms to user settings
// @ts-expect-error TS(7006): Parameter 'values' implicitly has an 'any' type.
const handleSubmit = async (values) => {
let body = new URLSearchParams();
body.append("key", "agreedToTerms");
body.append("value", values.agreedToTerms);

await axios.post("/admin-ng/user-settings/setting", body, {
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
setAgreedToTerms(true);
};

// If already accepted terms, dont display anything
if (agreedToTerms) {
return null;
}

// Else display terms
return (
<>
<div className="modal-animation modal-overlay" />
<section id="registration-modal" className="modal active modal-open modal-animation">
<header>
<h2>{t("TERMS.TITLE")}</h2>
</header>

<div className="modal-content" style={{ display: "block" }}>
<div className="modal-body">
<div>
<div className="row">
<div className="scrollbox">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(termsContent) }} ></div>
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
</div>
</div>
</div>
</div>
</div>

<Formik
initialValues={{}}
enableReinitialize
onSubmit={handleSubmit}
>
{(formik) => (<>
<div className="modal-content" style={{ display: "block" }}>
<div className="modal-body">
<div>
<fieldset>
<legend>{t("TERMS.TITLE")}</legend>
<div className="form-group form-group-checkbox">
<Field
type="checkbox"
name="agreedToTerms"
id="agreedToTerms"
className="form-control"
/>
<label htmlFor="agreedToTerms">
<span>{t("TERMS.AGREE")}</span>
</label>
</div>
</fieldset>
</div>
</div>
</div>

<footer>
<div className="pull-right">
<button
disabled={
// @ts-expect-error TS(2339): Property 'agreedToTerms' does not exist on type '... Remove this comment to see the full error message
!(formik.isValid && formik.values.agreedToTerms)
}
onClick={() => formik.handleSubmit()}
className={cn("submit", {
active:
// @ts-expect-error TS(2339): Property 'agreedToTerms' does not exist on type '... Remove this comment to see the full error message
formik.isValid && formik.values.agreedToTerms,
inactive: !(
// @ts-expect-error TS(2339): Property 'agreedToTerms' does not exist on type '... Remove this comment to see the full error message
formik.isValid && formik.values.agreedToTerms
),
})}
>
{t("SUBMIT")}
</button>
</div>
</footer>
</>)}
</Formik>
</section>
</>
);
};

export default TermsOfUseModal;
Original file line number Diff line number Diff line change
Expand Up @@ -2178,5 +2178,10 @@
"IMPRINT": "Imprint",
"PRIVACY": "Privacy statement",
"NOCONTENT": "Content not available"
},
"TERMS": {
"TITLE": "Terms of use",
"NOCONTENT": "Content not available",
"AGREE": "I have read and agree to the terms of use"
}
}
2 changes: 1 addition & 1 deletion src/styles/extensions/views/modals/_registration.scss
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
footer {

button {
margin: 15px 5px 15px 25px;
margin: 10px 5px 0px 25px;
&.inactive {
opacity: 0.5;
cursor: default;
Expand Down
Loading