1- import React , { useMemo } from 'react' ;
1+ import React , { useMemo , useEffect } from 'react' ;
22import { StudioFooterSlot } from '@edx/frontend-component-footer' ;
33import {
4- Add as AddIcon , EditOutline , DeleteOutline , AccessTime as ClockIcon ,
4+ Add as AddIcon , EditOutline , DeleteOutline , AccessTime as ClockIcon , Info ,
55} from '@openedx/paragon/icons' ;
66import {
77 Button ,
@@ -12,6 +12,7 @@ import {
1212 OverlayTrigger ,
1313 Tooltip ,
1414 ModalDialog ,
15+ Alert ,
1516} from '@openedx/paragon' ;
1617import { useIntl } from '@edx/frontend-platform/i18n' ;
1718import moment from 'moment' ;
@@ -40,8 +41,24 @@ const ReleaseNotes = () => {
4041 handleOpenUpdateForm,
4142 handleDeleteUpdateSubmit,
4243 handleOpenDeleteForm,
44+ errors,
4345 } = useReleaseNotes ( ) ;
4446
47+ useEffect ( ( ) => {
48+ const handleBeforeUnload = ( e ) => {
49+ if ( isFormOpen ) {
50+ e . preventDefault ( ) ;
51+ e . returnValue = '' ;
52+ }
53+ } ;
54+
55+ window . addEventListener ( 'beforeunload' , handleBeforeUnload ) ;
56+
57+ return ( ) => {
58+ window . removeEventListener ( 'beforeunload' , handleBeforeUnload ) ;
59+ } ;
60+ } , [ isFormOpen ] ) ;
61+
4562 const groups = useMemo ( ( ) => {
4663 const map = new Map ( ) ;
4764 ( notes || [ ] ) . forEach ( ( n ) => {
@@ -64,6 +81,13 @@ const ReleaseNotes = () => {
6481 return (
6582 < >
6683 < Header isHiddenMainMenu />
84+ { errors . loadingNotes && (
85+ < Container size = "xl" className = "px-4 pt-4" >
86+ < Alert variant = "danger" icon = { Info } >
87+ { intl . formatMessage ( messages . errorLoadingPage ) }
88+ </ Alert >
89+ </ Container >
90+ ) }
6791 < Container size = "xl" className = "release-notes-page px-4 pt-4" >
6892 < SubHeader
6993 title = { intl . formatMessage ( messages . headingTitle ) }
@@ -81,98 +105,100 @@ const ReleaseNotes = () => {
81105 ) : null }
82106 />
83107
84- < Layout
85- lg = { [ { span : 9 } , { span : 3 } ] }
86- md = { [ { span : 9 } , { span : 3 } ] }
87- xs = { [ { span : 12 } , { span : 12 } ] }
88- >
89- < Layout . Element >
90- < article >
91- < section className = "release-notes-list p-4.5" >
92- { groups . length > 0 ? (
93- groups . map ( ( g ) => (
94- < div key = { g . key } className = "mb-4" >
95- { g . items . map ( ( post ) => (
96- < div id = { `note-${ post . id } ` } key = { post . id } className = "release-note-item mb-4 pb-4" >
97- < div className = "d-flex justify-content-between align-items-start" >
98- < div >
99- < h3 className = "mb-4 pb-4" > { moment ( post . published_at ) . format ( 'MMMM D, YYYY' ) } </ h3 >
100- { post . published_at && moment ( post . published_at ) . isAfter ( moment ( ) ) && (
101- < OverlayTrigger
102- placement = "top"
103- overlay = { (
104- < Tooltip id = { `scheduled-tooltip-${ post . id } ` } >
105- { intl . formatMessage ( messages . scheduledTooltip , {
106- date : moment ( post . published_at ) . format ( 'MMMM D, YYYY h:mm A z' ) ,
107- } ) }
108- </ Tooltip >
108+ { ! errors . loadingNotes && (
109+ groups . length > 0 ? (
110+ < Layout
111+ lg = { [ { span : 9 } , { span : 3 } ] }
112+ md = { [ { span : 9 } , { span : 3 } ] }
113+ xs = { [ { span : 12 } , { span : 12 } ] }
114+ >
115+ < Layout . Element >
116+ < article >
117+ < section className = "release-notes-list p-4.5" >
118+ { groups . map ( ( g ) => (
119+ < div key = { g . key } className = "mb-4" >
120+ { g . items . map ( ( post ) => (
121+ < div id = { `note-${ post . id } ` } key = { post . id } className = "release-note-item mb-4 pb-4" >
122+ < div className = "d-flex justify-content-between align-items-start" >
123+ < div >
124+ < h3 className = "mb-4 pb-4" > { moment ( post . published_at ) . format ( 'MMMM D, YYYY' ) } </ h3 >
125+ { post . published_at && moment ( post . published_at ) . isAfter ( moment ( ) ) && (
126+ < OverlayTrigger
127+ placement = "top"
128+ overlay = { (
129+ < Tooltip id = { `scheduled-tooltip-${ post . id } ` } >
130+ { intl . formatMessage ( messages . scheduledTooltip , {
131+ date : moment ( post . published_at ) . format ( 'MMMM D, YYYY h:mm A z' ) ,
132+ } ) }
133+ </ Tooltip >
109134 ) }
110- >
111- < div className = "d-inline-flex align-items-center text-muted small mr-2" role = "button" tabIndex = { 0 } >
112- < Icon
113- className = "mr-1 p-0 justify-content-start scheduled-icon"
114- src = { ClockIcon }
115- alt = { intl . formatMessage ( messages . scheduledTooltip , {
116- date : moment ( post . published_at ) . format ( 'MMMM D, YYYY h:mm A z' ) ,
117- } ) }
118- />
119- < span > { intl . formatMessage ( messages . scheduledLabel ) } </ span >
135+ >
136+ < div className = "d-inline-flex align-items-center text-muted small mr-2" role = "button" tabIndex = { 0 } >
137+ < Icon
138+ className = "mr-1 p-0 justify-content-start scheduled-icon"
139+ src = { ClockIcon }
140+ alt = { intl . formatMessage ( messages . scheduledTooltip , {
141+ date : moment ( post . published_at ) . format ( 'MMMM D, YYYY h:mm A z' ) ,
142+ } ) }
143+ />
144+ < span > { intl . formatMessage ( messages . scheduledLabel ) } </ span >
145+ </ div >
146+ </ OverlayTrigger >
147+ ) }
148+ < div className = "d-flex align-items-center mb-1 justify-content-between" >
149+ < h6 className = "m-0" > { post . title } </ h6 >
150+ { administrator && (
151+ < div className = "ml-3 d-flex" >
152+ < IconButtonWithTooltip
153+ tooltipContent = { intl . formatMessage ( messages . editButton ) }
154+ src = { EditOutline }
155+ iconAs = { Icon }
156+ onClick = { ( ) => handleOpenUpdateForm ( REQUEST_TYPES . edit_update , post ) }
157+ data-testid = "release-note-edit-button"
158+ disabled = { isFormOpen }
159+ />
160+ < IconButtonWithTooltip
161+ tooltipContent = { intl . formatMessage ( messages . deleteButton ) }
162+ src = { DeleteOutline }
163+ iconAs = { Icon }
164+ onClick = { ( ) => handleOpenDeleteForm ( post ) }
165+ data-testid = "release-note-delete-button"
166+ disabled = { isFormOpen }
167+ />
168+ </ div >
169+ ) }
120170 </ div >
121- </ OverlayTrigger >
122- ) }
123- < div className = "d-flex align-items-center mb-1 justify-content-between" >
124- < h6 className = "m-0" > { post . title } </ h6 >
125- { administrator && (
126- < div className = "ml-3 d-flex" >
127- < IconButtonWithTooltip
128- tooltipContent = { intl . formatMessage ( messages . editButton ) }
129- src = { EditOutline }
130- iconAs = { Icon }
131- onClick = { ( ) => handleOpenUpdateForm ( REQUEST_TYPES . edit_update , post ) }
132- data-testid = "release-note-edit-button"
133- disabled = { isFormOpen }
134- />
135- < IconButtonWithTooltip
136- tooltipContent = { intl . formatMessage ( messages . deleteButton ) }
137- src = { DeleteOutline }
138- iconAs = { Icon }
139- onClick = { ( ) => handleOpenDeleteForm ( post ) }
140- data-testid = "release-note-delete-button"
141- disabled = { isFormOpen }
142- />
171+ { /* eslint-disable-next-line react/no-danger */ }
172+ < div className = "post-description" dangerouslySetInnerHTML = { { __html : post . description } } />
173+ { post . created_by && (
174+ < div className = "mt-3" >
175+ < small >
176+ { intl . formatMessage ( { id : 'release-notes.questions.contact' , defaultMessage : 'Questions? Contact {email}' } , {
177+ email : post . created_by ,
178+ } ) }
179+ </ small >
143180 </ div >
144181 ) }
145182 </ div >
146- { /* eslint-disable-next-line react/no-danger */ }
147- < div className = "post-description" dangerouslySetInnerHTML = { { __html : post . description } } />
148- { post . created_by && (
149- < div className = "mt-3" >
150- < small >
151- { intl . formatMessage ( { id : 'release-notes.questions.contact' , defaultMessage : 'Questions? Contact {email}' } , {
152- email : post . created_by ,
153- } ) }
154- </ small >
155- </ div >
156- ) }
157- </ div >
158183
184+ </ div >
159185 </ div >
160- </ div >
161- ) ) }
162- </ div >
163- ) )
164- ) : (
165- < div className = "text-center" >
166- < span className = "small mr-2" > { intl . formatMessage ( messages . noReleaseNotes ) } </ span >
167- </ div >
168- ) }
169- </ section >
170- </ article >
171- </ Layout . Element >
172- < Layout . Element >
173- < ReleaseNotesSidebar notes = { notes } / >
174- </ Layout . Element >
175- </ Layout >
186+ ) ) }
187+ </ div >
188+ ) ) }
189+ </ section >
190+ </ article >
191+ </ Layout . Element >
192+ < Layout . Element >
193+ < ReleaseNotesSidebar notes = { notes } / >
194+ </ Layout . Element >
195+ </ Layout >
196+ ) : (
197+ < div className = "text-center py-5" >
198+ < span className = "small" > { intl . formatMessage ( messages . noReleaseNotes ) } </ span >
199+ </ div >
200+ )
201+ ) }
176202 </ Container >
177203 { isFormOpen && (
178204 < ModalDialog
@@ -189,6 +215,11 @@ const ReleaseNotes = () => {
189215 </ ModalDialog . Title >
190216 </ ModalDialog . Header >
191217 < ModalDialog . Body >
218+ { ( errors . savingNotes || errors . creatingNote ) && (
219+ < Alert variant = "danger" icon = { Info } className = "mb-3" >
220+ { intl . formatMessage ( messages . errorSavingPost ) }
221+ </ Alert >
222+ ) }
192223 < ReleaseNoteForm
193224 initialValues = { notesInitialValues }
194225 close = { closeForm }
@@ -197,7 +228,12 @@ const ReleaseNotes = () => {
197228 </ ModalDialog . Body >
198229 </ ModalDialog >
199230 ) }
200- < DeleteModal isOpen = { isDeleteModalOpen } close = { closeDeleteModal } onDeleteSubmit = { handleDeleteUpdateSubmit } />
231+ < DeleteModal
232+ isOpen = { isDeleteModalOpen }
233+ close = { closeDeleteModal }
234+ onDeleteSubmit = { handleDeleteUpdateSubmit }
235+ errorDeleting = { errors . deletingNotes }
236+ />
201237 < StudioFooterSlot />
202238 </ >
203239 ) ;
0 commit comments