Skip to content

Commit 225d30f

Browse files
authored
Merge pull request #102 from AvaCodeSolutions/feat/101/viewer-ui-access
feat: #101 viewer UI access
2 parents e42e38e + 4fc88f8 commit 225d30f

File tree

8 files changed

+50
-36
lines changed

8 files changed

+50
-36
lines changed

django_email_learning/admin.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
Answer,
1111
CourseContent,
1212
Organization,
13+
OrganizationUser,
1314
BlockedEmail,
1415
)
1516

@@ -91,3 +92,4 @@ def get_content_title(self, obj: CourseContent) -> str | None:
9192
admin.site.register(Answer)
9293
admin.site.register(Organization)
9394
admin.site.register(BlockedEmail)
95+
admin.site.register(OrganizationUser)

frontend/course/Course.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function Course() {
2121
const [lessonCache, setLessonCache] = useState("")
2222
const [contentLoaded, setContentLoaded] = useState(false)
2323

24+
const userRole = localStorage.getItem('userRole');
2425
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
2526
const organizationId = localStorage.getItem('activeOrganizationId');
2627

@@ -120,7 +121,7 @@ function Course() {
120121
>
121122
<Grid size={{xs: 12, md: 9}} py={2} pl={2}>
122123
<Box p={2} sx={{ border: '1px solid', borderColor: 'grey.300', borderRadius: 1, minHeight: 300 }}>
123-
<Button variant="contained" startIcon={<DescriptionIcon />} sx={{ marginBottom: 2 }} onClick={() => {
124+
{userRole !== 'viewer' && <><Button variant="contained" startIcon={<DescriptionIcon />} sx={{ marginBottom: 2 }} onClick={() => {
124125
setDialogContent(<LessonForm
125126
header="New Lesson"
126127
initialContent={lessonCache}
@@ -134,7 +135,7 @@ function Course() {
134135
cancelCallback={() => setDialogOpen(false)}
135136
successCallback={resetDialog}
136137
courseId={course_id} />);
137-
setDialogOpen(true);}}>Add a Quiz</Button>
138+
setDialogOpen(true);}}>Add a Quiz</Button></> }
138139
<ContentTable courseId={course_id} loaded={contentLoaded} eventHandler={(event) => tableEventHandler(event)} />
139140
</Box>
140141
</Grid>

frontend/course/components/ContentTable.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const ContentTable = ({ courseId, eventHandler, loaded = false }) => {
99
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
1010
const organizationId = localStorage.getItem('activeOrganizationId');
1111

12+
const userRole = localStorage.getItem('userRole');
13+
1214
const formatPeriod = (period) => {
1315
if (!period) {
1416
return "";
@@ -97,7 +99,7 @@ const ContentTable = ({ courseId, eventHandler, loaded = false }) => {
9799
<TableCell>Waiting time</TableCell>
98100
<TableCell>type</TableCell>
99101
<TableCell>Published</TableCell>
100-
<TableCell align='right'>Actions</TableCell>
102+
{userRole !== 'viewer' && <TableCell align='right'>Actions</TableCell>}
101103
</TableRow>
102104
</TableHead>
103105
<TableBody>
@@ -108,12 +110,12 @@ const ContentTable = ({ courseId, eventHandler, loaded = false }) => {
108110
color='primary.dark' sx={{ cursor: 'pointer'}}>{content.title}</Typography></TableCell>
109111
<TableCell>{formatPeriod(content.waiting_period)}</TableCell>
110112
<TableCell>{content.type}</TableCell>
111-
<TableCell><Switch defaultChecked={content.is_published} onChange={() => TogglePublishContent(content.id, !content.is_published)} /></TableCell>
112-
<TableCell align='right'>
113+
<TableCell><Switch defaultChecked={content.is_published} onChange={() => TogglePublishContent(content.id, !content.is_published)} disabled={userRole == 'viewer'} /></TableCell>
114+
{userRole !== 'viewer' && <TableCell align='right'>
113115
<IconButton aria-label="delete" onClick={() => deleteContent(content.id)}>
114116
<DeleteIcon />
115117
</IconButton>
116-
</TableCell>
118+
</TableCell>}
117119
</TableRow>
118120
))}
119121
</TableBody>

frontend/course/components/LessonForm.jsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
1313
const [contentHelperText, setContentHelperText] = useState("");
1414
const [errorMessage, setErrorMessage] = useState("");
1515

16+
const userRole = localStorage.getItem('userRole');
1617
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
1718
const orgId = localStorage.getItem('activeOrganizationId');
1819

@@ -126,9 +127,9 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
126127
{errorMessage}
127128
</Alert>
128129
)}
129-
<RequiredTextField value={title} label="Lesson Title" name="lesson_title" sx={{ width: '100%' }} onChange={(e) => setTitle(e.target.value)} helperText={titleHelperText}/>
130+
<RequiredTextField value={title} label="Lesson Title" name="lesson_title" sx={{ width: '100%' }} onChange={(e) => setTitle(e.target.value)} helperText={titleHelperText} disabled={userRole === 'viewer'} />
130131
<Box sx={{ my: 2 }}>
131-
<ContentEditor initialContent={content} contentUpdateCallback={handleContentChange}/>
132+
<ContentEditor initialContent={content} contentUpdateCallback={handleContentChange} disabled={userRole === 'viewer'} />
132133
<Typography color="errorText.main" sx={{ marginTop: 1, fontSize: '0.75rem' }}>
133134
{contentHelperText}
134135
</Typography>
@@ -144,19 +145,20 @@ function LessonForm({ header, initialTitle, initialContent, onContentChange, can
144145
onChange={(e) => setWaitingPeriod(e.target.value)}
145146
sx={{ width: '200px', mr: 2 }}
146147
inputProps={{ min: 1 }}
148+
disabled={userRole === 'viewer'}
147149
/>
148-
<Select size="small" value={waitingPeriodUnit} onChange={(e) => setWaitingPeriodUnit(e.target.value)} name="waiting_period_unit" sx={{ width: '150px' }}>
150+
<Select size="small" value={waitingPeriodUnit} onChange={(e) => setWaitingPeriodUnit(e.target.value)} name="waiting_period_unit" sx={{ width: '150px' }} disabled={userRole === 'viewer'}>
149151
<MenuItem value="days">Days</MenuItem>
150152
<MenuItem value="hours">Hours</MenuItem>
151153
</Select>
152154
</Tooltip>
153155
<Box mt={2} textAlign="right">
154156
<Button variant="outlined" sx={{ mr: 1 }} onClick={cancel}>
155-
Cancel
157+
Back
156158
</Button>
157-
<Button type="submit" variant="contained" onClick={() => {if(!lessonId) { addLesson(); } else { updateLesson(); }}}>
159+
{userRole !== 'viewer' && <Button type="submit" variant="contained" onClick={() => {if(!lessonId) { addLesson(); } else { updateLesson(); }}}>
158160
Save Lesson
159-
</Button>
161+
</Button>}
160162
</Box>
161163
</Box>
162164
);

frontend/course/components/QuestionForm.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ const QuestionForm = ({question, index, eventHandler}) => {
1414
const [addingOption, setAddingOption] = useState(false);
1515
const optionInputRef = useRef(null);
1616

17+
const userRole = localStorage.getItem('userRole');
18+
1719
const editQuestion = () => {
18-
if (editMode && questionText.trim() === '') {
20+
if (editMode && questionText.trim() === '' || userRole === 'viewer') {
1921
return;
2022
}
2123
triggerUpdateEvent();
@@ -61,7 +63,7 @@ const QuestionForm = ({question, index, eventHandler}) => {
6163
<Box key={index} sx={{ mb: 1, p: 2, border: '1px solid', borderColor: 'grey.300', borderRadius: 1 }}>
6264
<Grid container spacing={2} alignItems="center">
6365
<Grid size={{ xs: 12, md: 9 }}>
64-
<EditIcon sx={{borderRadius: "50%", display: "inline-block", float: "left", mr: 1, fontSize: "0.9rem", border: 1, borderColor: "grey.200", color: "grey.400", padding: "4px", cursor: "pointer", ':hover': { backgroundColor: "primary.main", color: "white", borderColor: "primary.main" } }} onClick={editQuestion}/>
66+
{userRole !== 'viewer' && <EditIcon sx={{borderRadius: "50%", display: "inline-block", float: "left", mr: 1, fontSize: "0.9rem", border: 1, borderColor: "grey.200", color: "grey.400", padding: "4px", cursor: "pointer", ':hover': { backgroundColor: "primary.main", color: "white", borderColor: "primary.main" } }} onClick={editQuestion}/>}
6567
{!editMode ? (
6668
<Typography onClick={editQuestion}>{index + 1}. {questionText}</Typography>
6769
) : (
@@ -77,12 +79,12 @@ const QuestionForm = ({question, index, eventHandler}) => {
7779
)}
7880
</Grid>
7981
<Grid size={{ xs: 12, md: 3 }} sx={{ textAlign: 'right' }}>
80-
<Button variant="outlined" color="primary" sx={{ fontSize: '0.75rem', mt: 1 }} onClick={() => setAddingOption(true)} >
82+
{userRole !== 'viewer' && <><Button variant="outlined" color="primary" sx={{ fontSize: '0.75rem', mt: 1 }} onClick={() => setAddingOption(true)} >
8183
<RuleIcon /><Typography variant="button" sx={{ ml: 1, fontSize: '0.75rem' }}>Add Option</Typography>
8284
</Button>
8385
<Button variant="outlined" onClick={deleteCallback} sx={{ ml: 1, mt: 1, fontSize: '0.75rem' }}>
8486
Delete
85-
</Button>
87+
</Button></>}
8688
</Grid>
8789
{addingOption && (<>
8890
<Grid size={{ xs: 9 }} sx={{ display: 'flex', alignItems: 'center' }}>
@@ -125,7 +127,7 @@ const QuestionForm = ({question, index, eventHandler}) => {
125127
<TableRow>
126128
<TableCell>Options</TableCell>
127129
<TableCell>Correct Answer</TableCell>
128-
<TableCell align='right'>Actions</TableCell>
130+
{userRole !== 'viewer' && <TableCell align='right'>Actions</TableCell>}
129131
</TableRow>
130132
</TableHead>
131133
<TableBody>
@@ -146,16 +148,16 @@ const QuestionForm = ({question, index, eventHandler}) => {
146148
}}
147149
/>
148150
)}</TableCell>
149-
<TableCell><Switch onChange={(e)=>updateOption(idx, e.target.checked)} checked={option.isCorrect} /></TableCell>
150-
<TableCell align='right'>
151+
<TableCell><Switch onChange={(e)=>updateOption(idx, e.target.checked)} checked={option.isCorrect} disabled={userRole === 'viewer'} /></TableCell>
152+
{userRole !== 'viewer' && <TableCell align='right'>
151153
<EditIcon sx={{ cursor: 'pointer', mr: 1 }} onClick={() => {
152154
setOptions(options.map((opt, i) => i === idx ? { ...opt, editMode: !opt.editMode } : opt));
153155
}} />
154156
<ClearIcon sx={{ cursor: 'pointer' }} onClick={() => {
155157
const updatedOptions = options.filter((_, i) => i !== idx);
156158
setOptions(updatedOptions);
157159
}} />
158-
</TableCell>
160+
</TableCell>}
159161
</TableRow>
160162
))}
161163
</TableBody>

frontend/course/components/QuizForm.jsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
1919
const dialogRef = useRef(null);
2020
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
2121
const organizationId = localStorage.getItem('activeOrganizationId');
22-
22+
const userRole = localStorage.getItem('userRole');
2323

2424
const addQuiz = () => {
2525
if (!validateQuiz()) {
@@ -201,9 +201,9 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
201201
}} tabIndex={0} focusable="true">
202202
<Typography variant="h2" sx={{ my: 2, fontSize: '1.5rem' }}>{ quizId ? "Update Quiz" : "New Quiz" }</Typography>
203203
{errorMessage && <Alert severity="error" sx={{ mb: 2 }}>{errorMessage}</Alert>}
204-
<RequiredTextField label="Quiz Title" value={title} onChange={(e) => setTitle(e.target.value)} sx={{ mb: 2, width: '100%' }} />
205-
<Button variant="outlined" sx={{ mb: 2 }} onClick={() => setShowQuestionField(true)}>
206-
<QuizIcon sx={{ mr: 1 }} /> Add Question</Button>
204+
<RequiredTextField label="Quiz Title" value={title} onChange={(e) => setTitle(e.target.value)} sx={{ mb: 2, width: '100%' }} disabled={userRole === 'viewer'} />
205+
{userRole !== 'viewer' && <Button variant="outlined" sx={{ mb: 2 }} onClick={() => setShowQuestionField(true)}>
206+
<QuizIcon sx={{ mr: 1 }} /> Add Question</Button>}
207207
{ showQuestionField && (
208208
<Box sx={{ mb: 2, border: '1px solid', borderColor: 'grey.300', borderRadius: 1, p: 2 }}>
209209
<Grid container spacing={2} alignItems="center">
@@ -240,6 +240,7 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
240240
onChange={(e) => setRequiredScore(e.target.value)}
241241
sx={{ width: '200px', mr: 2 }}
242242
inputProps={{ min: 0, max: 100 }}
243+
disabled={userRole === 'viewer'}
243244
>
244245
</RequiredTextField>
245246
</Box>
@@ -254,19 +255,20 @@ const QuizForm = ({cancelCallback, successCallback, courseId, quizId, contentId,
254255
onChange={(e) => setWaitingPeriod(e.target.value)}
255256
sx={{ width: '200px', mr: 2 }}
256257
inputProps={{ min: 1 }}
258+
disabled={userRole === 'viewer'}
257259
/>
258-
<Select size="small" value={waitingPeriodUnit} onChange={(e) => setWaitingPeriodUnit(e.target.value)} name="waiting_period_unit" sx={{ width: '150px' }}>
260+
<Select size="small" value={waitingPeriodUnit} onChange={(e) => setWaitingPeriodUnit(e.target.value)} name="waiting_period_unit" sx={{ width: '150px' }} disabled={userRole === 'viewer'}>
259261
<MenuItem value="days">Days</MenuItem>
260262
<MenuItem value="hours">Hours</MenuItem>
261263
</Select>
262264
</Tooltip>
263265
<Box mt={2} textAlign="right">
264266
<Button variant="outlined" sx={{ mr: 1, boxShadow: 'none' }} onClick={cancel}>
265-
Cancel
267+
Back
266268
</Button>
267-
<Button type="submit" variant="contained" color="primary" sx={{ boxShadow: 'none' }} onClick={() => {if(!quizId) { addQuiz(); } else { updateQuiz(); }}}>
269+
{userRole !== 'viewer' && <Button type="submit" variant="contained" color="primary" sx={{ boxShadow: 'none' }} onClick={() => {if(!quizId) { addQuiz(); } else { updateQuiz(); }}}>
268270
Save Quiz
269-
</Button>
271+
</Button>}
270272
</Box>
271273
</Box>
272274
);

frontend/courses/Courses.jsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function Courses() {
2020
const [courses, setCourses] = useState([])
2121
const [organizationId, setOrganizationId] = useState(null);
2222
const [queryParameters, setQueryParameters] = useState("");
23+
const userRole = localStorage.getItem('userRole');
2324
const apiBaseUrl = localStorage.getItem('apiBaseUrl');
2425
const platformBaseUrl = localStorage.getItem('platformBaseUrl');
2526

@@ -106,23 +107,23 @@ function Courses() {
106107
>
107108
<Grid size={{xs: 12, md: 9}} py={2} pl={2}>
108109
<Box p={2} sx={{ border: '1px solid', borderColor: 'border.main', backgroundColor: 'background.main', borderRadius: 1, minHeight: 300 }}>
109-
<Button variant="contained" startIcon={<SchoolIcon />} sx={{ marginBottom: 2 }} onClick={() => {
110+
{userRole !== 'viewer' && <Button variant="contained" startIcon={<SchoolIcon />} sx={{ marginBottom: 2 }} onClick={() => {
110111
setDialogContent(<CourseForm
111112
successCallback={handleCourseCreated}
112113
failureCallback={handleCourseCreationFailed}
113114
cancelCallback={() => setDialogOpen(false)}
114115
activeOrganizationId={organizationId}
115116
createMode={true}
116117
/>);
117-
setDialogOpen(true);}}>Add a Course</Button>
118+
setDialogOpen(true);}}>Add a Course</Button>}
118119
<TableContainer component={Paper}>
119120
<Table sx={{ width: "100%" }} aria-label="Courses">
120121
<TableHead>
121122
<TableRow>
122123
<TableCell>Title</TableCell>
123124
<TableCell>Slug</TableCell>
124125
<TableCell>Enabled</TableCell>
125-
<TableCell align='right'>Actions</TableCell>
126+
{userRole !== 'viewer' && <TableCell align='right'>Actions</TableCell>}
126127
</TableRow>
127128
</TableHead>
128129
<TableBody>
@@ -140,9 +141,10 @@ function Courses() {
140141
checked={course.enabled}
141142
onChange={showEnableCoursePopup(course.id, course.enabled ? 'disable' : 'enable', course.title)}
142143
slotProps={{ input: { 'aria-label': course.enabled ? 'Disable Course' : 'Enable Course' } }}
144+
disabled={userRole === 'viewer'}
143145
/>
144146
</TableCell>
145-
<TableCell align="right">
147+
{userRole !== 'viewer' && <TableCell align="right">
146148
<IconButton onClick={() => {
147149
showEditCourseDialog(course);}}><EditIcon fontSize="small" /></IconButton>
148150
<IconButton aria-label={`Delete ${course.title}`} onClick={() => {
@@ -152,7 +154,7 @@ function Courses() {
152154
}} />);
153155
setDialogOpen(true);
154156
}}><DeleteIcon fontSize="small" /></IconButton>
155-
</TableCell>
157+
</TableCell>}
156158
</TableRow>
157159
))}
158160
</TableBody>

frontend/src/components/ContentEditor.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import FormatBoldIcon from '@mui/icons-material/FormatBold';
2020
import ImageIcon from '@mui/icons-material/Image';
2121

2222

23-
function ContentEditor({ initialContent, contentUpdateCallback }) {
23+
function ContentEditor({ initialContent, contentUpdateCallback, disabled = false }) {
2424
const editor = useEditor({
2525
extensions: [
2626
Document,
@@ -40,6 +40,7 @@ function ContentEditor({ initialContent, contentUpdateCallback }) {
4040
}),
4141
Dropcursor,],
4242
content: initialContent,
43+
editable: !disabled,
4344
autofocus: true,
4445
onUpdate: ({ editor }) => {
4546
contentUpdateCallback(editor.getHTML());
@@ -54,7 +55,7 @@ function ContentEditor({ initialContent, contentUpdateCallback }) {
5455
<Paper elevation={2} sx={{ width: '100%' }}>
5556
<EditorContext.Provider value={{ editor }}>
5657
{/* Material UI Toolbar */}
57-
<Toolbar variant="dense" sx={{
58+
{!disabled && <Toolbar variant="dense" sx={{
5859
backgroundColor: 'background.nav',
5960
borderBottom: '1px solid',
6061
borderColor: 'divider'
@@ -111,7 +112,7 @@ function ContentEditor({ initialContent, contentUpdateCallback }) {
111112
<CodeIcon />
112113
</IconButton>
113114
</Tooltip>
114-
</Toolbar>
115+
</Toolbar>}
115116

116117
{/* TipTap Editor wrapped in Material UI Box */}
117118
<Box

0 commit comments

Comments
 (0)