diff --git a/apps/api/src/security/rbac/rbac.py b/apps/api/src/security/rbac/rbac.py
index 8a16afd3..1e56238c 100644
--- a/apps/api/src/security/rbac/rbac.py
+++ b/apps/api/src/security/rbac/rbac.py
@@ -60,7 +60,11 @@ async def authorization_verify_if_user_is_author(
element_uuid: str,
db_session: Session,
):
- if action == "update" or "delete" or "read":
+ # For create action, we don't need to check existing resource
+ if action == "create":
+ return True # Allow creation if user is authenticated
+
+ if action in ["update", "delete", "read"]:
statement = select(ResourceAuthor).where(
ResourceAuthor.resource_uuid == element_uuid
)
@@ -79,6 +83,7 @@ async def authorization_verify_if_user_is_author(
return False
else:
return False
+ return False
# Tested and working
@@ -101,17 +106,17 @@ async def authorization_verify_based_on_roles(
user_roles_in_organization_and_standard_roles = db_session.exec(statement).all()
- # Find in roles list if there is a role that matches users action for this type of element
+ # Check all roles until we find one that grants the permission
for role in user_roles_in_organization_and_standard_roles:
role = Role.model_validate(role)
if role.rights:
rights = role.rights
- if rights[element_type][f"action_{action}"] is True:
+ element_rights = getattr(rights, element_type, None)
+ if element_rights and getattr(element_rights, f"action_{action}", False):
return True
- else:
- return False
- else:
- return False
+
+ # If we get here, no role granted the permission
+ return False
async def authorization_verify_based_on_org_admin_status(
@@ -133,13 +138,13 @@ async def authorization_verify_based_on_org_admin_status(
user_roles_in_organization_and_standard_roles = db_session.exec(statement).all()
- # Find in roles list if there is a role that matches users action for this type of element
+ # Check if user has admin role (role_id 1 or 2) in any organization
for role in user_roles_in_organization_and_standard_roles:
role = Role.model_validate(role)
- if role.id == 1 or role.id == 2:
+ if role.id in [1, 2]: # Assuming 1 and 2 are admin role IDs
return True
- else:
- return False
+
+ return False
# Tested and working
diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx
index 885d4ab4..c629dbf3 100644
--- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx
+++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/activity/[activityid]/activity.tsx
@@ -3,7 +3,7 @@ import Link from 'next/link'
import { getAPIUrl, getUriWithOrg } from '@services/config/config'
import Canva from '@components/Objects/Activities/DynamicCanva/DynamicCanva'
import VideoActivity from '@components/Objects/Activities/Video/Video'
-import { BookOpenCheck, Check, CheckCircle, ChevronDown, ChevronLeft, ChevronRight, FileText, Folder, List, Menu, MoreVertical, UserRoundPen, Video, Layers, ListFilter, ListTree, X, Edit2 } from 'lucide-react'
+import { BookOpenCheck, Check, CheckCircle, ChevronDown, ChevronLeft, ChevronRight, FileText, Folder, List, Menu, MoreVertical, UserRoundPen, Video, Layers, ListFilter, ListTree, X, Edit2, EllipsisVertical, Maximize2, Minimize2 } from 'lucide-react'
import { markActivityAsComplete, unmarkActivityAsComplete } from '@services/courses/activity'
import DocumentPdfActivity from '@components/Objects/Activities/DocumentPdf/DocumentPdf'
import ActivityIndicators from '@components/Pages/Courses/ActivityIndicators'
@@ -16,7 +16,7 @@ import { CourseProvider } from '@components/Contexts/CourseContext'
import AIActivityAsk from '@components/Objects/Activities/AI/AIActivityAsk'
import AIChatBotProvider from '@components/Contexts/AI/AIChatBotContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
-import React, { useEffect } from 'react'
+import React, { useEffect, useRef } from 'react'
import { getAssignmentFromActivityUUID, getFinalGrade, submitAssignmentForGrading } from '@services/courses/assignments'
import AssignmentStudentActivity from '@components/Objects/Activities/Assignment/AssignmentStudentActivity'
import { AssignmentProvider } from '@components/Contexts/Assignments/AssignmentContext'
@@ -33,6 +33,7 @@ import ActivityNavigation from '@components/Pages/Activity/ActivityNavigation'
import ActivityChapterDropdown from '@components/Pages/Activity/ActivityChapterDropdown'
import FixedActivitySecondaryBar from '@components/Pages/Activity/FixedActivitySecondaryBar'
import CourseEndView from '@components/Pages/Activity/CourseEndView'
+import { motion, AnimatePresence } from 'framer-motion'
interface ActivityClientProps {
activityid: string
@@ -42,6 +43,55 @@ interface ActivityClientProps {
course: any
}
+interface ActivityActionsProps {
+ activity: any
+ activityid: string
+ course: any
+ orgslug: string
+ assignment: any
+ showNavigation?: boolean
+}
+
+function ActivityActions({ activity, activityid, course, orgslug, assignment, showNavigation = true }: ActivityActionsProps) {
+ const session = useLHSession() as any;
+ const { contributorStatus } = useContributorStatus(course.course_uuid);
+
+ return (
+
+ {activity && activity.published == true && activity.content.paid_access != false && (
+
+ {activity.activity_type != 'TYPE_ASSIGNMENT' && (
+ <>
+
+ >
+ )}
+ {activity.activity_type == 'TYPE_ASSIGNMENT' && (
+ <>
+
+
+
+ >
+ )}
+ {showNavigation && (
+
+ )}
+
+ )}
+
+ );
+}
+
function ActivityClient(props: ActivityClientProps) {
const activityid = props.activityid
const courseuuid = props.courseuuid
@@ -55,8 +105,69 @@ function ActivityClient(props: ActivityClientProps) {
const [bgColor, setBgColor] = React.useState('bg-white')
const [assignment, setAssignment] = React.useState(null) as any;
const [markStatusButtonActive, setMarkStatusButtonActive] = React.useState(false);
+ const [isFocusMode, setIsFocusMode] = React.useState(false);
+ const isInitialRender = useRef(true);
const { contributorStatus } = useContributorStatus(courseuuid);
-
+ const router = useRouter();
+
+ // Function to find the current activity's position in the course
+ const findActivityPosition = () => {
+ let allActivities: any[] = [];
+ let currentIndex = -1;
+
+ // Flatten all activities from all chapters
+ course.chapters.forEach((chapter: any) => {
+ chapter.activities.forEach((activity: any) => {
+ const cleanActivityUuid = activity.activity_uuid?.replace('activity_', '');
+ allActivities.push({
+ ...activity,
+ cleanUuid: cleanActivityUuid,
+ chapterName: chapter.name
+ });
+
+ // Check if this is the current activity
+ if (cleanActivityUuid === activityid.replace('activity_', '')) {
+ currentIndex = allActivities.length - 1;
+ }
+ });
+ });
+
+ return { allActivities, currentIndex };
+ };
+
+ const { allActivities, currentIndex } = findActivityPosition();
+
+ // Get previous and next activities
+ const prevActivity = currentIndex > 0 ? allActivities[currentIndex - 1] : null;
+ const nextActivity = currentIndex < allActivities.length - 1 ? allActivities[currentIndex + 1] : null;
+
+ // Navigate to an activity
+ const navigateToActivity = (activity: any) => {
+ if (!activity) return;
+
+ const cleanCourseUuid = course.course_uuid?.replace('course_', '');
+ router.push(getUriWithOrg(orgslug, '') + `/course/${cleanCourseUuid}/activity/${activity.cleanUuid}`);
+ };
+
+ // Initialize focus mode from localStorage
+ React.useEffect(() => {
+ if (typeof window !== 'undefined') {
+ const saved = localStorage.getItem('globalFocusMode');
+ setIsFocusMode(saved === 'true');
+ }
+ }, []);
+
+ // Save focus mode to localStorage
+ React.useEffect(() => {
+ if (typeof window !== 'undefined') {
+ localStorage.setItem('globalFocusMode', isFocusMode.toString());
+ // Dispatch custom event for focus mode change
+ window.dispatchEvent(new CustomEvent('focusModeChange', {
+ detail: { isFocusMode }
+ }));
+ isInitialRender.current = false;
+ }
+ }, [isFocusMode]);
function getChapterNameByActivityId(course: any, activity_id: any) {
for (let i = 0; i < course.chapters.length; i++) {
@@ -78,43 +189,72 @@ function ActivityClient(props: ActivityClientProps) {
useEffect(() => {
if (activity.activity_type == 'TYPE_DYNAMIC') {
- setBgColor('bg-white nice-shadow');
+ setBgColor(isFocusMode ? 'bg-white' : 'bg-white nice-shadow');
}
else if (activity.activity_type == 'TYPE_ASSIGNMENT') {
setMarkStatusButtonActive(false);
- setBgColor('bg-white nice-shadow');
+ setBgColor(isFocusMode ? 'bg-white' : 'bg-white nice-shadow');
getAssignmentUI();
}
else {
- setBgColor('bg-zinc-950');
+ setBgColor(isFocusMode ? 'bg-zinc-950' : 'bg-zinc-950 nice-shadow');
}
}
- , [activity, pathname])
+ , [activity, pathname, isFocusMode])
return (
<>
-
- {activityid === 'end' ? (
-
- ) : (
-
-
-
-
-
+ {isFocusMode ? (
+
+
+ {/* Focus Mode Top Bar */}
+
+
+
+
+
setIsFocusMode(false)}
+ className="bg-white nice-shadow p-2 rounded-full cursor-pointer hover:bg-gray-50"
+ title="Exit focus mode"
+ >
+
+
+
+
+
+ {/* Center Course Info */}
+
-
Course
-
+ Course
+
{course.name}
-
+
+
+ {/* Progress Indicator */}
+
+
+
+
+ run.course_id === course.id)?.steps?.filter((step: any) => step.complete)?.length || 0) / (course.chapters?.reduce((acc: number, chapter: any) => acc + chapter.activities.length, 0) || 1))}
+ />
+
+
+
+ {Math.round(((course.trail?.runs?.find((run: any) => run.course_id === course.id)?.steps?.filter((step: any) => step.complete)?.length || 0) / (course.chapters?.reduce((acc: number, chapter: any) => acc + chapter.activities.length, 0) || 1)) * 100)}%
+
+
+
+
+ {course.trail?.runs?.find((run: any) => run.course_id === course.id)?.steps?.filter((step: any) => step.complete)?.length || 0} of {course.chapters?.reduce((acc: number, chapter: any) => acc + chapter.activities.length, 0) || 0}
+
+
+
+
-
+ {/* Focus Mode Content */}
+
+
+ {activity && activity.published == true && (
+ <>
+ {activity.content.paid_access == false ? (
+
+ ) : (
+
+ {/* Activity Types */}
+
+ {activity.activity_type == 'TYPE_DYNAMIC' && (
+
+ )}
+ {activity.activity_type == 'TYPE_VIDEO' && (
+
+ )}
+ {activity.activity_type == 'TYPE_DOCUMENT' && (
+
+ )}
+ {activity.activity_type == 'TYPE_ASSIGNMENT' && (
+
+ {assignment ? (
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ )}
+ >
+ )}
+
+
-
-
-
-
-
- Chapter : {getChapterNameByActivityId(course, activity.id)}
-
-
- {activity.name}
-
+ {/* Focus Mode Bottom Bar */}
+ {activity && activity.published == true && activity.content.paid_access != false && (
+
+
+
+
+
navigateToActivity(prevActivity)}
+ className={`flex items-center space-x-1.5 p-2 rounded-md transition-all duration-200 cursor-pointer ${
+ prevActivity
+ ? 'text-gray-700'
+ : 'opacity-50 text-gray-400 cursor-not-allowed'
+ }`}
+ disabled={!prevActivity}
+ title={prevActivity ? `Previous: ${prevActivity.name}` : 'No previous activity'}
+ >
+
+
+ Previous
+
+ {prevActivity ? prevActivity.name : 'No previous activity'}
+
+
+
+
+
+
+
navigateToActivity(nextActivity)}
+ className={`flex items-center space-x-1.5 p-2 rounded-md transition-all duration-200 cursor-pointer ${
+ nextActivity
+ ? 'text-gray-700'
+ : 'opacity-50 text-gray-400 cursor-not-allowed'
+ }`}
+ disabled={!nextActivity}
+ title={nextActivity ? `Next: ${nextActivity.name}` : 'No next activity'}
+ >
+
+ Next
+
+ {nextActivity ? nextActivity.name : 'No next activity'}
+
+
+
+
-
+
+
+ )}
+
+
+ ) : (
+
+ {/* Original non-focus mode UI */}
+ {activityid === 'end' ? (
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
Course
+
+ {course.name}
+
+
+
{activity && activity.published == true && activity.content.paid_access != false && (
- {activity.activity_type != 'TYPE_ASSIGNMENT' && (
- <>
-
- {contributorStatus === 'ACTIVE' && activity.activity_type == 'TYPE_DYNAMIC' && (
-
-
- Contribute to Activity
-
- )}
-
-
+
- >
- )}
- {activity.activity_type == 'TYPE_ASSIGNMENT' && (
- <>
-
-
-
-
- >
+
+
)}
)}
-
-
- {activity && activity.published == false && (
-
-
-
- This activity is not published yet
-
-
-
- )}
- {activity && activity.published == true && (
- <>
- {activity.content.paid_access == false ? (
-
- ) : (
-
- {/* Activity Types */}
-
- {activity.activity_type == 'TYPE_DYNAMIC' && (
-
- )}
- {activity.activity_type == 'TYPE_VIDEO' && (
-
- )}
- {activity.activity_type == 'TYPE_DOCUMENT' && (
-
- )}
- {activity.activity_type == 'TYPE_ASSIGNMENT' && (
-
- {assignment ? (
-
-
-
-
-
-
-
- ) : (
-
- )}
-
- )}
+
+
+
+
+
setIsFocusMode(true)}
+ className="bg-white nice-shadow p-2 rounded-full cursor-pointer hover:bg-gray-50 transition-all duration-200"
+ title="Enter focus mode"
+ >
+
+
+
+
+
+ Chapter : {getChapterNameByActivityId(course, activity.id)}
+
+
+ {activity.name}
+
- )}
- >
- )}
-
+
+ {activity && activity.published == true && activity.content.paid_access != false && (
+
+ {activity.activity_type != 'TYPE_ASSIGNMENT' && (
+ <>
+
+ {contributorStatus === 'ACTIVE' && activity.activity_type == 'TYPE_DYNAMIC' && (
+
+
+ Contribute
+
+ )}
+ >
+ )}
+
+ )}
+
+
+
- {/* Fixed Activity Secondary Bar */}
- {activity && activity.published == true && activity.content.paid_access != false && (
-
- )}
-
-
+ {activity && activity.published == false && (
+
+
+
+ This activity is not published yet
+
+
+
+ )}
+
+ {activity && activity.published == true && (
+ <>
+ {activity.content.paid_access == false ? (
+
+ ) : (
+
+ {/* Activity Types */}
+
+ {activity.activity_type == 'TYPE_DYNAMIC' && (
+
+ )}
+ {activity.activity_type == 'TYPE_VIDEO' && (
+
+ )}
+ {activity.activity_type == 'TYPE_DOCUMENT' && (
+
+ )}
+ {activity.activity_type == 'TYPE_ASSIGNMENT' && (
+
+ {assignment ? (
+
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+ )}
+
+
+ )}
+ >
+ )}
+
+ {/* Activity Actions below the content box */}
+ {activity && activity.published == true && activity.content.paid_access != false && (
+
+ )}
+
+ {/* Fixed Activity Secondary Bar */}
+ {activity && activity.published == true && activity.content.paid_access != false && (
+
+ )}
+
+
+
-
- )}
-
+ )}
+
+ )}
>
@@ -415,7 +771,6 @@ export function MarkStatus(props: {
status="warning"
/>
-
) : (
@@ -437,7 +792,6 @@ export function MarkStatus(props: {
)}{' '}
{!isMobile && {isLoading ? 'Marking...' : 'Mark as complete'} }
-
)}
>
@@ -483,15 +837,66 @@ function NextActivityButton({ course, currentActivityId, orgslug }: { course: an
};
return (
-
-
- {!isMobile && Next }
-
-
-
+
+ Next
+
+ {nextActivity.name}
+
+
+ );
+}
+
+function PreviousActivityButton({ course, currentActivityId, orgslug }: { course: any, currentActivityId: string, orgslug: string }) {
+ const router = useRouter();
+ const isMobile = useMediaQuery('(max-width: 768px)');
+
+ const findPreviousActivity = () => {
+ let allActivities: any[] = [];
+ let currentIndex = -1;
+
+ // Flatten all activities from all chapters
+ course.chapters.forEach((chapter: any) => {
+ chapter.activities.forEach((activity: any) => {
+ const cleanActivityUuid = activity.activity_uuid?.replace('activity_', '');
+ allActivities.push({
+ ...activity,
+ cleanUuid: cleanActivityUuid,
+ chapterName: chapter.name
+ });
+
+ // Check if this is the current activity
+ if (activity.id === currentActivityId) {
+ currentIndex = allActivities.length - 1;
+ }
+ });
+ });
+
+ // Get previous activity
+ return currentIndex > 0 ? allActivities[currentIndex - 1] : null;
+ };
+
+ const previousActivity = findPreviousActivity();
+
+ if (!previousActivity) return null;
+
+ const navigateToActivity = () => {
+ const cleanCourseUuid = course.course_uuid?.replace('course_', '');
+ router.push(getUriWithOrg(orgslug, '') + `/course/${cleanCourseUuid}/activity/${previousActivity.cleanUuid}`);
+ };
+
+ return (
+
+
+ Previous
+
+ {previousActivity.name}
+
);
}
diff --git a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx
index 236415c3..41dfbbb1 100644
--- a/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx
+++ b/apps/web/app/orgs/[orgslug]/(withmenu)/course/[courseuuid]/course.tsx
@@ -60,6 +60,16 @@ const CourseClient = (props: any) => {
useEffect(() => {
getLearningTags()
+
+ // Collapse chapters by default if more than 5 activities in total
+ if (course?.chapters) {
+ const totalActivities = course.chapters.reduce((sum: number, chapter: any) => sum + (chapter.activities?.length || 0), 0)
+ const defaultExpanded: {[key: string]: boolean} = {}
+ course.chapters.forEach((chapter: any) => {
+ defaultExpanded[chapter.chapter_uuid] = totalActivities <= 5
+ })
+ setExpandedChapters(defaultExpanded)
+ }
}, [org, course])
const getActivityTypeLabel = (activityType: string) => {
@@ -117,191 +127,52 @@ const CourseClient = (props: any) => {
) : (
<>
-
+
- {props.course?.thumbnail_image && org ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
About
-
-
- {learnings.length > 0 && learnings[0]?.text !== 'null' && (
-
-
- What you will learn
-
-
- {learnings.map((learning: any) => {
- // Handle both new format (object with text and emoji) and legacy format (string)
- const learningText = typeof learning === 'string' ? learning : learning.text
- const learningEmoji = typeof learning === 'string' ? null : learning.emoji
- const learningId = typeof learning === 'string' ? learning : learning.id || learning.text
-
- if (!learningText) return null
-
- return (
-
- )
- })}
-
-
+
+
+ {props.course?.thumbnail_image && org ? (
+
+ ) : (
+
)}
-
Course Lessons
-
- {course.chapters.map((chapter: any) => {
- const isExpanded = expandedChapters[chapter.chapter_uuid] ?? true; // Default to expanded
- return (
-
-
setExpandedChapters(prev => ({
- ...prev,
- [chapter.chapter_uuid]: !isExpanded
- }))}
- >
-
{chapter.name}
-
-
- {chapter.activities.length} Activities
-
-
-
-
-
-
-
-
- {chapter.activities.map((activity: any) => {
- return (
-
-
-
-
- {isActivityDone(activity) ? (
-
-
-
-
- ) : (
-
-
-
- )}
-
-
-
-
{activity.name}
- {isActivityCurrent(activity) && (
-
- Current
-
- )}
-
-
- {activity.activity_type === 'TYPE_DYNAMIC' && (
-
- )}
- {activity.activity_type === 'TYPE_VIDEO' && (
-
- )}
- {activity.activity_type === 'TYPE_DOCUMENT' && (
-
- )}
- {activity.activity_type === 'TYPE_ASSIGNMENT' && (
-
- )}
- {getActivityTypeLabel(activity.activity_type)}
-
-
-
-
-
-
- )
- })}
-
-
-
- )
- })}
+ {course?.trail?.runs?.find((run: any) => run.course_id == course.id) && (
+
+ )}
+
+
-
+
+
{/* Actions Box */}
@@ -313,6 +184,145 @@ const CourseClient = (props: any) => {
+
+ {learnings.length > 0 && learnings[0]?.text !== 'null' && (
+
+
What you will learn
+
+ {learnings.map((learning: any) => {
+ // Handle both new format (object with text and emoji) and legacy format (string)
+ const learningText = typeof learning === 'string' ? learning : learning.text
+ const learningEmoji = typeof learning === 'string' ? null : learning.emoji
+ const learningId = typeof learning === 'string' ? learning : learning.id || learning.text
+
+ if (!learningText) return null
+
+ return (
+
+ )
+ })}
+
+
+ )}
+
+
+
Course Lessons
+
+ {course.chapters.map((chapter: any) => {
+ const isExpanded = expandedChapters[chapter.chapter_uuid] ?? true; // Default to expanded
+ return (
+
+
setExpandedChapters(prev => ({
+ ...prev,
+ [chapter.chapter_uuid]: !isExpanded
+ }))}
+ >
+
{chapter.name}
+
+
+ {chapter.activities.length} Activities
+
+
+
+
+
+
+
+
+ {chapter.activities.map((activity: any) => {
+ return (
+
+
+
+ {isActivityDone(activity) ? (
+
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
{activity.name}
+ {isActivityCurrent(activity) && (
+
+ Current
+
+ )}
+
+
+ {activity.activity_type === 'TYPE_DYNAMIC' && (
+
+ )}
+ {activity.activity_type === 'TYPE_VIDEO' && (
+
+ )}
+ {activity.activity_type === 'TYPE_DOCUMENT' && (
+
+ )}
+ {activity.activity_type === 'TYPE_ASSIGNMENT' && (
+
+ )}
+ {getActivityTypeLabel(activity.activity_type)}
+
+
+
+
+
+ )
+ })}
+
+
+
+ )
+ })}
+
+
{isMobile && (
diff --git a/apps/web/components/Objects/Editor/Extensions/Image/ImageBlock.ts b/apps/web/components/Objects/Editor/Extensions/Image/ImageBlock.ts
index caff7013..abc7dc15 100644
--- a/apps/web/components/Objects/Editor/Extensions/Image/ImageBlock.ts
+++ b/apps/web/components/Objects/Editor/Extensions/Image/ImageBlock.ts
@@ -17,6 +17,9 @@ export default Node.create({
size: {
width: 300,
},
+ alignment: {
+ default: 'center',
+ },
}
},
diff --git a/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx b/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx
index fb985d78..5227b95c 100644
--- a/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx
+++ b/apps/web/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx
@@ -1,7 +1,7 @@
import { NodeViewWrapper } from '@tiptap/react'
import React, { useEffect } from 'react'
import { Resizable } from 're-resizable'
-import { AlertTriangle, Image, Download } from 'lucide-react'
+import { AlertTriangle, Image, Download, AlignLeft, AlignCenter, AlignRight } from 'lucide-react'
import { uploadNewImageFile } from '../../../../../services/blocks/Image/images'
import { getActivityBlockMediaDirectory } from '@services/media/media'
import { useOrg } from '@components/Contexts/OrgContext'
@@ -29,10 +29,12 @@ function ImageBlockComponent(props: any) {
const [imageSize, setImageSize] = React.useState({
width: props.node.attrs.size ? props.node.attrs.size.width : 300,
})
+ const [alignment, setAlignment] = React.useState(props.node.attrs.alignment || 'center')
const fileId = blockObject
? `${blockObject.content.file_id}.${blockObject.content.file_format}`
: null
+
const handleImageChange = (event: React.ChangeEvent
) => {
setImage(event.target.files[0])
}
@@ -49,6 +51,7 @@ function ImageBlockComponent(props: any) {
props.updateAttributes({
blockObject: object,
size: imageSize,
+ alignment: alignment,
})
}
@@ -75,8 +78,26 @@ function ImageBlockComponent(props: any) {
document.body.removeChild(link);
};
+ const handleAlignmentChange = (newAlignment: string) => {
+ setAlignment(newAlignment);
+ props.updateAttributes({
+ alignment: newAlignment,
+ });
+ };
+
useEffect(() => {}, [course, org])
+ const getAlignmentClass = () => {
+ switch (alignment) {
+ case 'left':
+ return 'justify-start';
+ case 'right':
+ return 'justify-end';
+ default:
+ return 'justify-center';
+ }
+ };
+
return (
@@ -85,7 +106,7 @@ function ImageBlockComponent(props: any) {
{blockObject && isEditable && (
-
+
-
+
+
+
+
handleAlignmentChange('left')}
+ className={`p-1.5 rounded-md hover:bg-gray-100 text-gray-600 ${alignment === 'left' ? 'bg-gray-100' : ''}`}
+ title="Align left"
+ >
+
+
+
handleAlignmentChange('center')}
+ className={`p-1.5 rounded-md hover:bg-gray-100 text-gray-600 ${alignment === 'center' ? 'bg-gray-100' : ''}`}
+ title="Center align"
+ >
+
+
+
handleAlignmentChange('right')}
+ className={`p-1.5 rounded-md hover:bg-gray-100 text-gray-600 ${alignment === 'right' ? 'bg-gray-100' : ''}`}
+ title="Align right"
+ >
+
+
+
+
)}
{blockObject && !isEditable && (
-
+
{
const orgslug = props.orgslug
@@ -17,6 +18,41 @@ export const OrgMenu = (props: any) => {
const [feedbackModal, setFeedbackModal] = React.useState(false)
const org = useOrg() as any;
const [isMenuOpen, setIsMenuOpen] = React.useState(false)
+ const [isFocusMode, setIsFocusMode] = useState(false)
+ const pathname = usePathname()
+
+ useEffect(() => {
+ // Only check focus mode if we're in an activity page
+ if (typeof window !== 'undefined' && pathname?.includes('/activity/')) {
+ const saved = localStorage.getItem('globalFocusMode');
+ setIsFocusMode(saved === 'true');
+ } else {
+ setIsFocusMode(false);
+ }
+
+ // Add storage event listener for cross-window changes
+ const handleStorageChange = (e: StorageEvent) => {
+ if (e.key === 'globalFocusMode' && pathname?.includes('/activity/')) {
+ setIsFocusMode(e.newValue === 'true');
+ }
+ };
+
+ // Add custom event listener for same-window changes
+ const handleFocusModeChange = (e: CustomEvent) => {
+ if (pathname?.includes('/activity/')) {
+ setIsFocusMode(e.detail.isFocusMode);
+ }
+ };
+
+ window.addEventListener('storage', handleStorageChange);
+ window.addEventListener('focusModeChange', handleFocusModeChange as EventListener);
+
+ // Cleanup
+ return () => {
+ window.removeEventListener('storage', handleStorageChange);
+ window.removeEventListener('focusModeChange', handleFocusModeChange as EventListener);
+ };
+ }, [pathname]);
function closeFeedbackModal() {
setFeedbackModal(false)
@@ -26,6 +62,11 @@ export const OrgMenu = (props: any) => {
setIsMenuOpen(!isMenuOpen)
}
+ // Only hide menu if we're in an activity page and focus mode is enabled
+ if (pathname?.includes('/activity/') && isFocusMode) {
+ return null;
+ }
+
return (
<>
diff --git a/apps/web/components/Pages/Activity/FixedActivitySecondaryBar.tsx b/apps/web/components/Pages/Activity/FixedActivitySecondaryBar.tsx
index 41679d4e..ae86377a 100644
--- a/apps/web/components/Pages/Activity/FixedActivitySecondaryBar.tsx
+++ b/apps/web/components/Pages/Activity/FixedActivitySecondaryBar.tsx
@@ -160,11 +160,7 @@ export default function FixedActivitySecondaryBar(props: FixedActivitySecondaryB
navigateToActivity(nextActivity)}
- className={`flex items-center space-x-1 sm:space-x-2 py-1.5 px-1.5 sm:px-2 rounded-md transition-all duration-200 ${
- nextActivity
- ? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
- : 'text-gray-300 cursor-not-allowed'
- }`}
+ className={`flex items-center space-x-1 sm:space-x-2 py-1.5 px-1.5 sm:px-2 rounded-md transition-all duration-200`}
disabled={!nextActivity}
title={nextActivity ? `Next: ${nextActivity.name}` : 'No next activity'}
>
diff --git a/apps/web/hooks/useContributorStatus.ts b/apps/web/hooks/useContributorStatus.ts
index de44d4c6..851320bb 100644
--- a/apps/web/hooks/useContributorStatus.ts
+++ b/apps/web/hooks/useContributorStatus.ts
@@ -5,6 +5,11 @@ import toast from 'react-hot-toast';
export type ContributorStatus = 'NONE' | 'PENDING' | 'ACTIVE' | 'INACTIVE';
+interface Contributor {
+ user_id: string;
+ authorship_status: ContributorStatus;
+}
+
export function useContributorStatus(courseUuid: string) {
const session = useLHSession() as any;
const [contributorStatus, setContributorStatus] = useState('NONE');
@@ -22,9 +27,9 @@ export function useContributorStatus(courseUuid: string) {
session.data?.tokens?.access_token
);
- if (response && response.data) {
+ if (response && response.data && Array.isArray(response.data)) {
const currentUser = response.data.find(
- (contributor: any) => contributor.user_id === session.data.user.id
+ (contributor: Contributor) => contributor.user_id === session.data.user.id
);
if (currentUser) {
@@ -32,10 +37,13 @@ export function useContributorStatus(courseUuid: string) {
} else {
setContributorStatus('NONE');
}
+ } else {
+ setContributorStatus('NONE');
}
} catch (error) {
console.error('Failed to check contributor status:', error);
toast.error('Failed to check contributor status');
+ setContributorStatus('NONE');
} finally {
setIsLoading(false);
}
diff --git a/apps/web/package.json b/apps/web/package.json
index 4f594216..5fd4bd82 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -63,7 +63,7 @@
"katex": "^0.16.21",
"lowlight": "^3.3.0",
"lucide-react": "^0.453.0",
- "next": "15.2.4",
+ "next": "15.3.1",
"next-auth": "^4.24.11",
"nextjs-toploader": "^1.6.12",
"prosemirror-state": "^1.4.3",
diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml
index a058da32..b7290c38 100644
--- a/apps/web/pnpm-lock.yaml
+++ b/apps/web/pnpm-lock.yaml
@@ -169,14 +169,14 @@ importers:
specifier: ^0.453.0
version: 0.453.0(react@19.0.0)
next:
- specifier: 15.2.4
- version: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ specifier: 15.3.1
+ version: 15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-auth:
specifier: ^4.24.11
- version: 4.24.11(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 4.24.11(next@15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
nextjs-toploader:
specifier: ^1.6.12
- version: 1.6.12(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 1.6.12(next@15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
prosemirror-state:
specifier: ^1.4.3
version: 1.4.3
@@ -412,158 +412,268 @@ packages:
cpu: [arm64]
os: [darwin]
+ '@img/sharp-darwin-arm64@0.34.1':
+ resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
'@img/sharp-darwin-x64@0.33.5':
resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
+ '@img/sharp-darwin-x64@0.34.1':
+ resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
'@img/sharp-libvips-darwin-arm64@1.0.4':
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
cpu: [arm64]
os: [darwin]
+ '@img/sharp-libvips-darwin-arm64@1.1.0':
+ resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==}
+ cpu: [arm64]
+ os: [darwin]
+
'@img/sharp-libvips-darwin-x64@1.0.4':
resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
cpu: [x64]
os: [darwin]
+ '@img/sharp-libvips-darwin-x64@1.1.0':
+ resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==}
+ cpu: [x64]
+ os: [darwin]
+
'@img/sharp-libvips-linux-arm64@1.0.4':
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
+ '@img/sharp-libvips-linux-arm64@1.1.0':
+ resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==}
+ cpu: [arm64]
+ os: [linux]
+
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
+ '@img/sharp-libvips-linux-arm@1.1.0':
+ resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.1.0':
+ resolution: {integrity: sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==}
+ cpu: [ppc64]
+ os: [linux]
+
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
+ '@img/sharp-libvips-linux-s390x@1.1.0':
+ resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==}
+ cpu: [s390x]
+ os: [linux]
+
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
+ '@img/sharp-libvips-linux-x64@1.1.0':
+ resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==}
+ cpu: [x64]
+ os: [linux]
+
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
+ '@img/sharp-libvips-linuxmusl-arm64@1.1.0':
+ resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==}
+ cpu: [arm64]
+ os: [linux]
+
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
+ '@img/sharp-libvips-linuxmusl-x64@1.1.0':
+ resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==}
+ cpu: [x64]
+ os: [linux]
+
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
+ '@img/sharp-linux-arm64@0.34.1':
+ resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
+ '@img/sharp-linux-arm@0.34.1':
+ resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
+ '@img/sharp-linux-s390x@0.34.1':
+ resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
+ '@img/sharp-linux-x64@0.34.1':
+ resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
+ '@img/sharp-linuxmusl-arm64@0.34.1':
+ resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
+ '@img/sharp-linuxmusl-x64@0.34.1':
+ resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
+ '@img/sharp-wasm32@0.34.1':
+ resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
'@img/sharp-win32-ia32@0.33.5':
resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
+ '@img/sharp-win32-ia32@0.34.1':
+ resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
'@img/sharp-win32-x64@0.33.5':
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
+ '@img/sharp-win32-x64@0.34.1':
+ resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
'@napi-rs/wasm-runtime@0.2.8':
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==}
- '@next/env@15.2.4':
- resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==}
+ '@next/env@15.3.1':
+ resolution: {integrity: sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==}
'@next/eslint-plugin-next@15.2.1':
resolution: {integrity: sha512-6ppeToFd02z38SllzWxayLxjjNfzvc7Wm07gQOKSLjyASvKcXjNStZrLXMHuaWkhjqxe+cnhb2uzfWXm1VEj/Q==}
- '@next/swc-darwin-arm64@15.2.4':
- resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==}
+ '@next/swc-darwin-arm64@15.3.1':
+ resolution: {integrity: sha512-hjDw4f4/nla+6wysBL07z52Gs55Gttp5Bsk5/8AncQLJoisvTBP0pRIBK/B16/KqQyH+uN4Ww8KkcAqJODYH3w==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@next/swc-darwin-x64@15.2.4':
- resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==}
+ '@next/swc-darwin-x64@15.3.1':
+ resolution: {integrity: sha512-q+aw+cJ2ooVYdCEqZVk+T4Ni10jF6Fo5DfpEV51OupMaV5XL6pf3GCzrk6kSSZBsMKZtVC1Zm/xaNBFpA6bJ2g==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@next/swc-linux-arm64-gnu@15.2.4':
- resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==}
+ '@next/swc-linux-arm64-gnu@15.3.1':
+ resolution: {integrity: sha512-wBQ+jGUI3N0QZyWmmvRHjXjTWFy8o+zPFLSOyAyGFI94oJi+kK/LIZFJXeykvgXUk1NLDAEFDZw/NVINhdk9FQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@next/swc-linux-arm64-musl@15.2.4':
- resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==}
+ '@next/swc-linux-arm64-musl@15.3.1':
+ resolution: {integrity: sha512-IIxXEXRti/AulO9lWRHiCpUUR8AR/ZYLPALgiIg/9ENzMzLn3l0NSxVdva7R/VDcuSEBo0eGVCe3evSIHNz0Hg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@next/swc-linux-x64-gnu@15.2.4':
- resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==}
+ '@next/swc-linux-x64-gnu@15.3.1':
+ resolution: {integrity: sha512-bfI4AMhySJbyXQIKH5rmLJ5/BP7bPwuxauTvVEiJ/ADoddaA9fgyNNCcsbu9SlqfHDoZmfI6g2EjzLwbsVTr5A==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@next/swc-linux-x64-musl@15.2.4':
- resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==}
+ '@next/swc-linux-x64-musl@15.3.1':
+ resolution: {integrity: sha512-FeAbR7FYMWR+Z+M5iSGytVryKHiAsc0x3Nc3J+FD5NVbD5Mqz7fTSy8CYliXinn7T26nDMbpExRUI/4ekTvoiA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@next/swc-win32-arm64-msvc@15.2.4':
- resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==}
+ '@next/swc-win32-arm64-msvc@15.3.1':
+ resolution: {integrity: sha512-yP7FueWjphQEPpJQ2oKmshk/ppOt+0/bB8JC8svPUZNy0Pi3KbPx2Llkzv1p8CoQa+D2wknINlJpHf3vtChVBw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@next/swc-win32-x64-msvc@15.2.4':
- resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==}
+ '@next/swc-win32-x64-msvc@15.3.1':
+ resolution: {integrity: sha512-3PMvF2zRJAifcRNni9uMk/gulWfWS+qVI/pagd+4yLF5bcXPZPPH2xlYRYOsUjmCJOXSTAC2PjRzbhsRzR2fDQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -2768,8 +2878,8 @@ packages:
nodemailer:
optional: true
- next@15.2.4:
- resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==}
+ next@15.3.1:
+ resolution: {integrity: sha512-8+dDV0xNLOgHlyBxP1GwHGVaNXsmp+2NhZEYrXr24GWLHtt27YrBPbPuHvzlhi7kZNYjeJNR93IF5zfFu5UL0g==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
@@ -3216,6 +3326,10 @@ packages:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ sharp@0.34.1:
+ resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -3657,76 +3771,154 @@ snapshots:
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
+ '@img/sharp-darwin-arm64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.1.0
+ optional: true
+
'@img/sharp-darwin-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
+ '@img/sharp-darwin-x64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.1.0
+ optional: true
+
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
+ '@img/sharp-libvips-darwin-arm64@1.1.0':
+ optional: true
+
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
+ '@img/sharp-libvips-darwin-x64@1.1.0':
+ optional: true
+
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
+ '@img/sharp-libvips-linux-arm64@1.1.0':
+ optional: true
+
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
+ '@img/sharp-libvips-linux-arm@1.1.0':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.1.0':
+ optional: true
+
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
+ '@img/sharp-libvips-linux-s390x@1.1.0':
+ optional: true
+
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
+ '@img/sharp-libvips-linux-x64@1.1.0':
+ optional: true
+
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
+ '@img/sharp-libvips-linuxmusl-arm64@1.1.0':
+ optional: true
+
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
+ '@img/sharp-libvips-linuxmusl-x64@1.1.0':
+ optional: true
+
'@img/sharp-linux-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
+ '@img/sharp-linux-arm64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.1.0
+ optional: true
+
'@img/sharp-linux-arm@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
+ '@img/sharp-linux-arm@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.1.0
+ optional: true
+
'@img/sharp-linux-s390x@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
+ '@img/sharp-linux-s390x@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.1.0
+ optional: true
+
'@img/sharp-linux-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
+ '@img/sharp-linux-x64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.1.0
+ optional: true
+
'@img/sharp-linuxmusl-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
+ '@img/sharp-linuxmusl-arm64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.1.0
+ optional: true
+
'@img/sharp-linuxmusl-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
+ '@img/sharp-linuxmusl-x64@0.34.1':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.1.0
+ optional: true
+
'@img/sharp-wasm32@0.33.5':
dependencies:
'@emnapi/runtime': 1.4.0
optional: true
+ '@img/sharp-wasm32@0.34.1':
+ dependencies:
+ '@emnapi/runtime': 1.4.0
+ optional: true
+
'@img/sharp-win32-ia32@0.33.5':
optional: true
+ '@img/sharp-win32-ia32@0.34.1':
+ optional: true
+
'@img/sharp-win32-x64@0.33.5':
optional: true
+ '@img/sharp-win32-x64@0.34.1':
+ optional: true
+
'@napi-rs/wasm-runtime@0.2.8':
dependencies:
'@emnapi/core': 1.4.0
@@ -3734,34 +3926,34 @@ snapshots:
'@tybys/wasm-util': 0.9.0
optional: true
- '@next/env@15.2.4': {}
+ '@next/env@15.3.1': {}
'@next/eslint-plugin-next@15.2.1':
dependencies:
fast-glob: 3.3.1
- '@next/swc-darwin-arm64@15.2.4':
+ '@next/swc-darwin-arm64@15.3.1':
optional: true
- '@next/swc-darwin-x64@15.2.4':
+ '@next/swc-darwin-x64@15.3.1':
optional: true
- '@next/swc-linux-arm64-gnu@15.2.4':
+ '@next/swc-linux-arm64-gnu@15.3.1':
optional: true
- '@next/swc-linux-arm64-musl@15.2.4':
+ '@next/swc-linux-arm64-musl@15.3.1':
optional: true
- '@next/swc-linux-x64-gnu@15.2.4':
+ '@next/swc-linux-x64-gnu@15.3.1':
optional: true
- '@next/swc-linux-x64-musl@15.2.4':
+ '@next/swc-linux-x64-musl@15.3.1':
optional: true
- '@next/swc-win32-arm64-msvc@15.2.4':
+ '@next/swc-win32-arm64-msvc@15.3.1':
optional: true
- '@next/swc-win32-x64-msvc@15.2.4':
+ '@next/swc-win32-x64-msvc@15.3.1':
optional: true
'@nodelib/fs.scandir@2.1.5':
@@ -6119,13 +6311,13 @@ snapshots:
natural-compare@1.4.0: {}
- next-auth@4.24.11(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ next-auth@4.24.11(next@15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@babel/runtime': 7.27.0
'@panva/hkdf': 1.2.1
cookie: 0.7.2
jose: 4.15.9
- next: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ next: 15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
oauth: 0.9.15
openid-client: 5.7.1
preact: 10.26.5
@@ -6134,9 +6326,9 @@ snapshots:
react-dom: 19.0.0(react@19.0.0)
uuid: 8.3.2
- next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ next@15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
- '@next/env': 15.2.4
+ '@next/env': 15.3.1
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.15
busboy: 1.6.0
@@ -6146,23 +6338,23 @@ snapshots:
react-dom: 19.0.0(react@19.0.0)
styled-jsx: 5.1.6(react@19.0.0)
optionalDependencies:
- '@next/swc-darwin-arm64': 15.2.4
- '@next/swc-darwin-x64': 15.2.4
- '@next/swc-linux-arm64-gnu': 15.2.4
- '@next/swc-linux-arm64-musl': 15.2.4
- '@next/swc-linux-x64-gnu': 15.2.4
- '@next/swc-linux-x64-musl': 15.2.4
- '@next/swc-win32-arm64-msvc': 15.2.4
- '@next/swc-win32-x64-msvc': 15.2.4
+ '@next/swc-darwin-arm64': 15.3.1
+ '@next/swc-darwin-x64': 15.3.1
+ '@next/swc-linux-arm64-gnu': 15.3.1
+ '@next/swc-linux-arm64-musl': 15.3.1
+ '@next/swc-linux-x64-gnu': 15.3.1
+ '@next/swc-linux-x64-musl': 15.3.1
+ '@next/swc-win32-arm64-msvc': 15.3.1
+ '@next/swc-win32-x64-msvc': 15.3.1
'@opentelemetry/api': 1.9.0
- sharp: 0.33.5
+ sharp: 0.34.1
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
- nextjs-toploader@1.6.12(next@15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ nextjs-toploader@1.6.12(next@15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
- next: 15.2.4(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ next: 15.3.1(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
nprogress: 0.2.0
prop-types: 15.8.1
react: 19.0.0
@@ -6666,6 +6858,34 @@ snapshots:
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
+ sharp@0.34.1:
+ dependencies:
+ color: 4.2.3
+ detect-libc: 2.0.3
+ semver: 7.7.1
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.1
+ '@img/sharp-darwin-x64': 0.34.1
+ '@img/sharp-libvips-darwin-arm64': 1.1.0
+ '@img/sharp-libvips-darwin-x64': 1.1.0
+ '@img/sharp-libvips-linux-arm': 1.1.0
+ '@img/sharp-libvips-linux-arm64': 1.1.0
+ '@img/sharp-libvips-linux-ppc64': 1.1.0
+ '@img/sharp-libvips-linux-s390x': 1.1.0
+ '@img/sharp-libvips-linux-x64': 1.1.0
+ '@img/sharp-libvips-linuxmusl-arm64': 1.1.0
+ '@img/sharp-libvips-linuxmusl-x64': 1.1.0
+ '@img/sharp-linux-arm': 0.34.1
+ '@img/sharp-linux-arm64': 0.34.1
+ '@img/sharp-linux-s390x': 0.34.1
+ '@img/sharp-linux-x64': 0.34.1
+ '@img/sharp-linuxmusl-arm64': 0.34.1
+ '@img/sharp-linuxmusl-x64': 0.34.1
+ '@img/sharp-wasm32': 0.34.1
+ '@img/sharp-win32-ia32': 0.34.1
+ '@img/sharp-win32-x64': 0.34.1
+ optional: true
+
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0