Skip to content

Commit c1b98e3

Browse files
committed
Merge branch '296-terms-of-use' of schuettloeffel-elsa/opencast-admin-interface into develop
Pull request #825 Optional terms of use dialog for user
2 parents 14e1e59 + e6a0e74 commit c1b98e3

4 files changed

Lines changed: 169 additions & 1 deletion

File tree

src/components/Header.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { availableHotkeys } from "../configs/hotkeysConfig";
1414
import { studioURL } from "../configs/generalConfig";
1515
import { hasAccess } from "../utils/utils";
1616
import RegistrationModal from "./shared/RegistrationModal";
17+
import TermsOfUseModal from "./shared/TermsOfUseModal";
1718
import HotKeyCheatSheet from "./shared/HotKeyCheatSheet";
1819
import { useHotkeys } from "react-hotkeys-hook";
1920
import { useAppDispatch, useAppSelector } from "../store";
@@ -51,6 +52,7 @@ const Header = () => {
5152
const errorCounter = useAppSelector(state => getErrorCount(state));
5253
const user = useAppSelector(state => getUserInformation(state));
5354
const orgProperties = useAppSelector(state => getOrgProperties(state));
55+
const displayTerms = (orgProperties["org.opencastproject.admin.display_terms"] || "false").toLowerCase() === "true";
5456

5557
const loadHealthStatus = async () => {
5658
await dispatch(fetchHealthStatus());
@@ -267,6 +269,9 @@ const Header = () => {
267269
{/* Adopters Registration Modal */}
268270
<RegistrationModal modalRef={registrationModalRef}/>
269271

272+
{/* Terms of use for all non-admin users */}
273+
{displayTerms && !user.roles.includes("ROLE_ADMIN") && <TermsOfUseModal />}
274+
270275
{/* Hotkey Cheat Sheet */}
271276
<HotKeyCheatSheet modalRef={hotKeyCheatSheetModalRef}/>
272277
</>
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { useEffect, useState } from "react";
2+
import { useTranslation } from "react-i18next";
3+
import { Field, Formik } from "formik";
4+
import cn from "classnames";
5+
import axios from "axios";
6+
import i18n from "../../i18n/i18n";
7+
import DOMPurify from "dompurify";
8+
9+
// Generate URL for terms based on the languae
10+
const getURL = (language: string) => {
11+
return `/ui/config/admin-ui/terms.${language}.html`;
12+
};
13+
14+
const TermsOfUseModal = () => {
15+
const { t } = useTranslation();
16+
const [termsContent, setTermsContent] = useState("");
17+
const [agreedToTerms, setAgreedToTerms] = useState(true);
18+
19+
// Check if already accepted terms
20+
useEffect(() => {
21+
const checkTerms = async () => {
22+
try {
23+
type FetchUserSettings = {
24+
total: number,
25+
offset: number,
26+
limit: number,
27+
results: {
28+
id: number,
29+
value: string,
30+
key: string
31+
}[]
32+
};
33+
const response = await axios.get<FetchUserSettings>("/admin-ng/user-settings/settings.json");
34+
const agreedResult = response.data.results.find(result => result.key === "agreedToTerms");
35+
const isAgreed = agreedResult?.value === "true";
36+
setAgreedToTerms(isAgreed);
37+
} catch (error) {
38+
console.error("Error while retrieving data: ", error);
39+
setAgreedToTerms(false);
40+
}
41+
};
42+
43+
checkTerms();
44+
}, []);
45+
46+
// Fetch terms
47+
useEffect(() => {
48+
axios.get<string>(getURL(i18n.language))
49+
.then(response => {
50+
setTermsContent(response.data);
51+
})
52+
.catch(() => {
53+
axios.get<string>(getURL(typeof i18n.options.fallbackLng === "string" ? i18n.options.fallbackLng : "en-US"))
54+
.then(response => {
55+
setTermsContent(response.data);
56+
})
57+
.catch(error => {
58+
console.error("Error while fetching data:", error);
59+
setTermsContent(t("TERMS.NOCONTENT"));
60+
});
61+
});
62+
// eslint-disable-next-line react-hooks/exhaustive-deps
63+
}, [agreedToTerms]); // Listen to changes in agreedToTerms
64+
65+
// Set terms to user settings
66+
const handleSubmit = async (values: {agreedToTerms: boolean}) => {
67+
const body = new URLSearchParams();
68+
body.append("key", "agreedToTerms");
69+
body.append("value", values.agreedToTerms ? "true" : "false");
70+
71+
await axios.post("/admin-ng/user-settings/setting", body, {
72+
headers: {
73+
"Content-Type": "application/x-www-form-urlencoded",
74+
},
75+
});
76+
setAgreedToTerms(true);
77+
};
78+
79+
// If already accepted terms, dont display anything
80+
if (agreedToTerms) {
81+
return null;
82+
}
83+
84+
// Else display terms
85+
return (
86+
<>
87+
<div className="modal-animation modal-overlay" />
88+
<section id="registration-modal" className="modal active modal-open modal-animation">
89+
<header>
90+
<h2>{t("TERMS.TITLE")}</h2>
91+
</header>
92+
93+
<div className="modal-content" style={{ display: "block" }}>
94+
<div className="modal-body">
95+
<div>
96+
<div className="row">
97+
<div className="scrollbox">
98+
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(termsContent) }} ></div>
99+
</div>
100+
</div>
101+
</div>
102+
</div>
103+
</div>
104+
105+
<Formik
106+
initialValues={{ agreedToTerms: false }}
107+
enableReinitialize
108+
onSubmit={handleSubmit}
109+
>
110+
{formik => (<>
111+
<div className="modal-content" style={{ display: "block" }}>
112+
<div className="modal-body">
113+
<div>
114+
<fieldset>
115+
<legend>{t("TERMS.TITLE")}</legend>
116+
<div className="form-group form-group-checkbox">
117+
<Field
118+
type="checkbox"
119+
name="agreedToTerms"
120+
id="agreedToTerms"
121+
className="form-control"
122+
/>
123+
<label htmlFor="agreedToTerms">
124+
<span>{t("TERMS.AGREE")}</span>
125+
</label>
126+
</div>
127+
</fieldset>
128+
</div>
129+
</div>
130+
</div>
131+
132+
<footer>
133+
<div className="pull-right">
134+
<button
135+
disabled={
136+
!(formik.isValid && formik.values.agreedToTerms)
137+
}
138+
onClick={() => formik.handleSubmit()}
139+
className={cn("submit", {
140+
active:
141+
formik.isValid && formik.values.agreedToTerms,
142+
inactive: !(
143+
formik.isValid && formik.values.agreedToTerms
144+
),
145+
})}
146+
>
147+
{t("SUBMIT")}
148+
</button>
149+
</div>
150+
</footer>
151+
</>)}
152+
</Formik>
153+
</section>
154+
</>
155+
);
156+
};
157+
158+
export default TermsOfUseModal;

src/i18n/org/opencastproject/adminui/languages/lang-en_US.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2155,5 +2155,10 @@
21552155
"IMPRINT": "Imprint",
21562156
"PRIVACY": "Privacy statement",
21572157
"NOCONTENT": "Content not available"
2158+
},
2159+
"TERMS": {
2160+
"TITLE": "Terms of use",
2161+
"NOCONTENT": "Content not available",
2162+
"AGREE": "I have read and agree to the terms of use"
21582163
}
21592164
}

src/styles/extensions/views/modals/_registration.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
footer {
211211

212212
button {
213-
margin: 15px 5px 15px 25px;
213+
margin: 10px 5px 0px 25px;
214214
&.inactive {
215215
opacity: 0.5;
216216
cursor: default;

0 commit comments

Comments
 (0)