chore: update queue dashboard authentication and improve course component handling

- Replaced email/token-based authentication with HTTP Basic Auth for the queue dashboard in production.
- Updated course components to use courseId instead of courseSlug for better consistency.
- Adjusted role-based security checks to utilize isStudentExperience for conditional rendering.
- Enhanced view-as-student functionality to redirect correctly based on courseId.
This commit is contained in:
Rotimi Best
2026-06-07 20:32:37 +01:00
parent 3561dc0fda
commit 48ce4d4d64
8 changed files with 45 additions and 64 deletions
@@ -80,6 +80,7 @@
<ViewAsStudentModal
bind:open={viewAsStudentOpen}
courseId={courseApi.course?.id}
courseSlug={courseApi.course?.slug}
currentOrgDomain={$currentOrgDomain}
/>
@@ -6,11 +6,12 @@
interface Props {
open?: boolean;
courseId?: string | null;
courseSlug?: string | null;
currentOrgDomain?: string;
}
let { open = $bindable(false), courseSlug = null, currentOrgDomain = '' }: Props = $props();
let { open = $bindable(false), courseId = null, courseSlug = null, currentOrgDomain = '' }: Props = $props();
let isNavigating = $state(false);
@@ -24,7 +25,7 @@
isNavigating = true;
// Opens the student view in a new tab; close the modal once the handoff starts.
const ok = await viewAsStudent({ courseSlug, currentOrgDomain });
const ok = await viewAsStudent({ courseId, currentOrgDomain });
isNavigating = false;
if (ok) {
@@ -59,7 +60,7 @@
<Button variant="secondary" onclick={handleCopyLink} disabled={!courseSlug || isNavigating}>
{$t('course.view_as_student.copy_link')}
</Button>
<Button variant="default" onclick={handleGoToLms} disabled={!courseSlug} loading={isNavigating}>
<Button variant="default" onclick={handleGoToLms} disabled={!courseId} loading={isNavigating}>
{$t('course.view_as_student.go_to_lms')}
</Button>
</Dialog.Footer>
@@ -30,7 +30,7 @@
import { ZExerciseUpdate } from '@cio/utils/validation/exercise';
import { mapZodErrorsToTranslations } from '$lib/utils/validation';
import { transformQuestionsToApiFormat } from '$features/course/components/exercise/functions';
import { isOrgStudent } from '$lib/utils/store/app';
import { isOrgStudent, isStudentExperience } from '$lib/utils/store/app';
import { t } from '$lib/utils/functions/translations';
import { snackbar } from '$features/ui/snackbar/store';
import { exerciseApi } from '$features/course/api';
@@ -400,7 +400,7 @@
</Page.HeaderContent>
<Page.Action>
<div class="flex items-center gap-2">
{#if $isOrgStudent && courseApi.course?.id && exerciseId}
{#if $isStudentExperience && courseApi.course?.id && exerciseId}
<ContentNavigationActions courseId={courseApi.course.id} {exerciseId} />
{/if}
@@ -501,7 +501,7 @@
<Page.Body>
{#snippet child()}
<div class="overflow-x-hidden pb-20">
{#if $isOrgStudent}
{#if $isStudentExperience}
{#if isExerciseUnlocked}
<ViewMode {preview} {exerciseId} isFetchingExercise={isFetching} {mySubmissions} />
{:else}
@@ -15,7 +15,7 @@
import MODES from '$lib/utils/constants/mode';
import { profile } from '$lib/utils/store/user';
import { isOrgStudent } from '$lib/utils/store/app';
import { isOrgStudent, isStudentExperience } from '$lib/utils/store/app';
import { currentOrg } from '$lib/utils/store/org';
import { snackbar } from '$features/ui/snackbar/store';
import { RefreshPageData, UnsavedChanges } from '$features/ui';
@@ -376,7 +376,7 @@
</Page.HeaderContent>
<Page.Action>
<div class="flex items-center gap-2">
{#if mode === MODES.view && $isOrgStudent}
{#if mode === MODES.view && $isStudentExperience}
<ContentNavigationActions {lessonId} {courseId} />
{/if}
@@ -10,7 +10,7 @@ interface OpenCoursePreviewOptions {
}
interface ViewAsStudentOptions {
courseSlug?: string | null;
courseId?: string | null;
currentOrgDomain?: string;
}
@@ -57,14 +57,12 @@ export function openCoursePreview({ courseId, courseSlug, currentOrgDomain = ''
* dashboard's own `hooks.server.ts` proxies `/api/auth/*` to the API. Either way the
* cookie lands on `currentOrgDomain` and the relative `redirect` resolves there.
*/
export async function viewAsStudent({ courseSlug, currentOrgDomain = '' }: ViewAsStudentOptions) {
export async function viewAsStudent({ courseId, currentOrgDomain = '' }: ViewAsStudentOptions) {
if (typeof window === 'undefined') {
return false;
}
if (!courseSlug) {
snackbar.info('course.header.preview_missing_slug');
if (!courseId) {
return false;
}
@@ -78,7 +76,7 @@ export async function viewAsStudent({ courseSlug, currentOrgDomain = '' }: ViewA
const origin = currentOrgDomain?.trim() || window.location.origin;
const loginLinkUrl = new URL('/api/auth/login-link', origin);
loginLinkUrl.searchParams.set('token', token);
loginLinkUrl.searchParams.set('redirect', `/course/${courseSlug}`);
loginLinkUrl.searchParams.set('redirect', `/courses/${courseId}/lessons?next=true`);
// Cross-origin handoff — open in a new tab so the teacher keeps their dashboard.
window.open(loginLinkUrl.toString(), '_blank', 'noopener,noreferrer');
@@ -7,7 +7,7 @@
interface Props {
allowedRoles?: number[];
onDenied?: any;
onDenied?: () => void;
// Since orgAdmin can see all, we need a way to show the content only to students
onlyStudent?: boolean;
children?: import('svelte').Snippet;