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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/node_modules
/build
.vscode
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
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 */}
{displayTerms && <TermsOfUseModal />}
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved

{/* Hotkey Cheat Sheet */}
{displayHotKeyCheatSheet && (
<HotKeyCheatSheet close={hideHotKeyCheatSheet} />
Expand Down
155 changes: 155 additions & 0 deletions src/components/shared/TermsOfUseModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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";


const TermsOfUseModal = () => {
const { t } = useTranslation();
const initialValues = {};
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
const [termsContent, setTermsContent] = useState<string>("");
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
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.some(result => result.key === "agreedToTerms" && result.value === "true");
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
setAgreedToTerms(isAgreed);
} catch (error) {
console.error("Fehler beim Abrufen der Daten:", error);
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
setAgreedToTerms(false);
}
};

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

// Fetch terms
useEffect(() => {
const getURL = (language: string) => {
return `ui/config/admin-ui/terms.${language}.html`;
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
};
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved

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").toString());
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
});
});
// 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={initialValues}
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
enableReinitialize
onSubmit={(values) => handleSubmit(values)}
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
>
{(formik) => (
<>
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
<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
),
})}
>
Submit
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
</button>
</div>
</footer>
</>
)}
schuettloeffel-elsa marked this conversation as resolved.
Show resolved Hide resolved
</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"
}
}
Loading