Skip to content

Optional terms of use dialog for user #825

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
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
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 () => {
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();
}, []);

// Fetch terms
useEffect(() => {
axios.get(getURL(i18n.language))
.then(response => {
setTermsContent(response.data);
})
.catch(error => {
axios.get(getURL(typeof i18n.options.fallbackLng === 'string' ? i18n.options.fallbackLng : 'en-US'))
.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 ? "true" : "false");

await axios.post("/admin-ng/user-settings/setting", body, {
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>
</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