diff --git a/frontend/src/components/ExerciseInlineEmbedModal.jsx b/frontend/src/components/ExerciseInlineEmbedModal.jsx index 6357bdb..a5a0ea5 100644 --- a/frontend/src/components/ExerciseInlineEmbedModal.jsx +++ b/frontend/src/components/ExerciseInlineEmbedModal.jsx @@ -9,7 +9,40 @@ import { sanitizeInlineMediaSize, } from '../constants/inlineExerciseMedia' import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption' -import RightsDeclarationDialog from './RightsDeclarationDialog' + +const DECL_INIT = { + rights_holder_confirmed: false, + contains_identifiable_persons: null, + person_consent_confirmed: false, + contains_minors: null, + parental_consent_confirmed: false, + contains_music: null, + music_rights_confirmed: false, + contains_third_party_content: null, + third_party_rights_confirmed: false, +} + +function validateDecl(decl) { + if (!decl.rights_holder_confirmed) + return 'Bitte bestätigen, dass du die erforderlichen Rechte an diesem Medium besitzt.' + if (decl.contains_identifiable_persons === null) + return 'Bitte angeben, ob erkennbare Personen abgebildet sind.' + if (decl.contains_identifiable_persons && !decl.person_consent_confirmed) + return 'Bitte bestätigen, dass die Einwilligungen aller erkennbaren Personen vorliegen.' + if (decl.contains_minors === null) + return 'Bitte angeben, ob Minderjährige abgebildet sind.' + if (decl.contains_minors && !decl.parental_consent_confirmed) + return 'Bitte bestätigen, dass die Einwilligungen der Sorgeberechtigten vorliegen.' + if (decl.contains_music === null) + return 'Bitte angeben, ob das Medium Musik enthält.' + if (decl.contains_music && !decl.music_rights_confirmed) + return 'Bitte bestätigen, dass die erforderlichen Musikrechte vorliegen.' + if (decl.contains_third_party_content === null) + return 'Bitte angeben, ob fremde geschützte Inhalte enthalten sind.' + if (decl.contains_third_party_content && !decl.third_party_rights_confirmed) + return 'Bitte bestätigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen.' + return '' +} /** * @param {{ @@ -31,27 +64,32 @@ export default function ExerciseInlineEmbedModal({ const [title, setTitle] = useState('') const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE) const [busy, setBusy] = useState(false) - const [rightsDialogOpen, setRightsDialogOpen] = useState(false) + const [decl, setDecl] = useState({ ...DECL_INIT }) + const [declErr, setDeclErr] = useState('') + + const setDeclField = (key, val) => setDecl((d) => ({ ...d, [key]: val })) useEffect(() => { if (!open) return setUrl('') setTitle('') setDisplaySize(DEFAULT_INLINE_MEDIA_SIZE) + setDecl({ ...DECL_INIT }) + setDeclErr('') }, [open]) - const submit = () => { + const submit = async () => { const u = url.trim() if (!u) { alert('Bitte eine Embed-URL eingeben (https://…).') return } - setRightsDialogOpen(true) - } - - const doSubmitWithDecl = async (decl) => { - setRightsDialogOpen(false) - const u = url.trim() + const validErr = validateDecl(decl) + if (validErr) { + setDeclErr(validErr) + return + } + setDeclErr('') const size = sanitizeInlineMediaSize(displaySize) const fd = new FormData() fd.append('embed_url', u) @@ -86,21 +124,13 @@ export default function ExerciseInlineEmbedModal({ if (!open) return null return ( - <> - setRightsDialogOpen(false)} - onConfirm={doSubmitWithDecl} - targetVisibility="private" - mode="upload" - /> -
e.target === e.currentTarget && !busy && !rightsDialogOpen && onClose()}> +
e.target === e.currentTarget && !busy && onClose()}>
e.stopPropagation()} >
@@ -111,7 +141,7 @@ export default function ExerciseInlineEmbedModal({ Schließen
-
+
))} -
+ +
+
- ) } diff --git a/frontend/src/components/ExerciseInlineFileMediaModal.jsx b/frontend/src/components/ExerciseInlineFileMediaModal.jsx index 8c84633..75ebeda 100644 --- a/frontend/src/components/ExerciseInlineFileMediaModal.jsx +++ b/frontend/src/components/ExerciseInlineFileMediaModal.jsx @@ -10,7 +10,6 @@ import { sanitizeInlineMediaSize, } from '../constants/inlineExerciseMedia' import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption' -import RightsDeclarationDialog from './RightsDeclarationDialog' function RtePickerAssetThumb({ asset }) { const id = asset.id @@ -61,6 +60,40 @@ function inferExerciseMediaType(file) { return 'image' } +const DECL_INIT = { + rights_holder_confirmed: false, + contains_identifiable_persons: null, + person_consent_confirmed: false, + contains_minors: null, + parental_consent_confirmed: false, + contains_music: null, + music_rights_confirmed: false, + contains_third_party_content: null, + third_party_rights_confirmed: false, +} + +function validateDecl(decl) { + if (!decl.rights_holder_confirmed) + return 'Bitte bestätigen, dass du die erforderlichen Rechte an diesem Medium besitzt.' + if (decl.contains_identifiable_persons === null) + return 'Bitte angeben, ob erkennbare Personen abgebildet sind.' + if (decl.contains_identifiable_persons && !decl.person_consent_confirmed) + return 'Bitte bestätigen, dass die Einwilligungen aller erkennbaren Personen vorliegen.' + if (decl.contains_minors === null) + return 'Bitte angeben, ob Minderjährige abgebildet sind.' + if (decl.contains_minors && !decl.parental_consent_confirmed) + return 'Bitte bestätigen, dass die Einwilligungen der Sorgeberechtigten vorliegen.' + if (decl.contains_music === null) + return 'Bitte angeben, ob das Medium Musik enthält.' + if (decl.contains_music && !decl.music_rights_confirmed) + return 'Bitte bestätigen, dass die erforderlichen Musikrechte vorliegen.' + if (decl.contains_third_party_content === null) + return 'Bitte angeben, ob fremde geschützte Inhalte enthalten sind.' + if (decl.contains_third_party_content && !decl.third_party_rights_confirmed) + return 'Bitte bestätigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen.' + return '' +} + /** * @param {{ * open: boolean, @@ -90,7 +123,10 @@ export default function ExerciseInlineFileMediaModal({ const [uploadTitle, setUploadTitle] = useState('') const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE) const [uploadInputKey, setUploadInputKey] = useState(0) - const [rightsDialogOpen, setRightsDialogOpen] = useState(false) + const [decl, setDecl] = useState({ ...DECL_INIT }) + const [declErr, setDeclErr] = useState('') + + const setDeclField = (key, val) => setDecl((d) => ({ ...d, [key]: val })) const assetToExerciseMedia = useMemo(() => { const m = new Map() @@ -128,6 +164,8 @@ export default function ExerciseInlineFileMediaModal({ setUploadInputKey((k) => k + 1) setDisplaySize(DEFAULT_INLINE_MEDIA_SIZE) setErr(null) + setDecl({ ...DECL_INIT }) + setDeclErr('') const t = setTimeout(loadAssets, 280) return () => clearTimeout(t) }, [open]) @@ -184,16 +222,17 @@ export default function ExerciseInlineFileMediaModal({ } } - const handleUploadAndInsert = () => { + const handleUploadAndInsert = async () => { if (!uploadFile) { alert('Bitte eine Datei wählen.') return } - setRightsDialogOpen(true) - } - - const doUploadWithDecl = async (decl) => { - setRightsDialogOpen(false) + const validErr = validateDecl(decl) + if (validErr) { + setDeclErr(validErr) + return + } + setDeclErr('') const size = sanitizeInlineMediaSize(displaySize) const inferred = inferExerciseMediaType(uploadFile) const fd = new FormData() @@ -242,15 +281,7 @@ export default function ExerciseInlineFileMediaModal({ if (!open) return null return ( - <> - setRightsDialogOpen(false)} - onConfirm={doUploadWithDecl} - targetVisibility="private" - mode="upload" - /> -
e.target === e.currentTarget && !busy && !rightsDialogOpen && onClose()}> +
e.target === e.currentTarget && !busy && onClose()}>
{!loading && items.length === 0 ? ( -

Keine Treffer — Suche anpassen oder „Neu hochladen“.

+

Keine Treffer — Suche anpassen oder „Neu hochladen".

) : null} )} @@ -376,6 +407,127 @@ export default function ExerciseInlineFileMediaModal({ onChange={(e) => setUploadTitle(e.target.value)} disabled={busy} /> + + {/* P-06 Rechte-Erklärung */} +
+

+ Rechte-Erklärung (VORLÄUFIG – p06-v1-conservative) +

+ +
+ setDeclField('rights_holder_confirmed', e.target.checked)} + disabled={busy} + style={{ marginTop: '3px', flexShrink: 0 }} + /> + +
+ +
+ Erkennbare Personen abgebildet? * +
+ + +
+ {decl.contains_identifiable_persons === true && ( +
+ setDeclField('person_consent_confirmed', e.target.checked)} + disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} /> + +
+ )} +
+ +
+ Minderjährige abgebildet? * +
+ + +
+ {decl.contains_minors === true && ( +
+ setDeclField('parental_consent_confirmed', e.target.checked)} + disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} /> + +
+ )} +
+ +
+ Musik enthalten? * +
+ + +
+ {decl.contains_music === true && ( +
+ setDeclField('music_rights_confirmed', e.target.checked)} + disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} /> + +
+ )} +
+ +
+ Fremde geschützte Inhalte (Logos, Grafiken, Fotos Dritter)? * +
+ + +
+ {decl.contains_third_party_content === true && ( +
+ setDeclField('third_party_rights_confirmed', e.target.checked)} + disabled={busy} style={{ marginTop: '3px', flexShrink: 0 }} /> + +
+ )} +
+ + {declErr && ( +

{declErr}

+ )} +
)}
@@ -410,6 +562,5 @@ export default function ExerciseInlineFileMediaModal({
- ) }