diff --git a/apps/api/src/routers/courses/activities/activities.py b/apps/api/src/routers/courses/activities/activities.py index c8e58c08..7ac20cf5 100644 --- a/apps/api/src/routers/courses/activities/activities.py +++ b/apps/api/src/routers/courses/activities/activities.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List from fastapi import APIRouter, Depends, UploadFile, Form, Request from src.db.courses.activities import ActivityCreate, ActivityRead, ActivityUpdate from src.db.users import PublicUser @@ -113,7 +113,7 @@ async def api_create_video_activity( request: Request, name: str = Form(), chapter_id: str = Form(), - details: Optional[dict] = Form(default=None), + details: str = Form(default="{}"), current_user: PublicUser = Depends(get_current_user), video_file: UploadFile | None = None, db_session=Depends(get_db_session), diff --git a/apps/api/src/services/courses/activities/video.py b/apps/api/src/services/courses/activities/video.py index 1ef9c13f..71408033 100644 --- a/apps/api/src/services/courses/activities/video.py +++ b/apps/api/src/services/courses/activities/video.py @@ -1,4 +1,5 @@ -from typing import Literal, Optional +from typing import Literal +import json from src.db.courses.courses import Course from src.db.organizations import Organization @@ -31,7 +32,7 @@ async def create_video_activity( current_user: PublicUser, db_session: Session, video_file: UploadFile | None = None, - details: Optional[dict] = None, + details: str = "{}", ): # RBAC check await rbac_check(request, "activity_x", current_user, "create", db_session) @@ -40,6 +41,9 @@ async def create_video_activity( statement = select(Chapter).where(Chapter.id == chapter_id) chapter = db_session.exec(statement).first() + # convert details to dict + details = json.loads(details) + if not chapter: raise HTTPException( status_code=404, @@ -146,7 +150,7 @@ class ExternalVideo(BaseModel): uri: str type: Literal["youtube", "vimeo"] chapter_id: str - details: Optional[dict] = None + details: str = "{}" class ExternalVideoInDB(BaseModel): @@ -184,6 +188,9 @@ async def create_external_video_activity( # generate activity_uuid activity_uuid = str(f"activity_{uuid4()}") + # convert details to dict + details = json.loads(data.details) + activity_object = Activity( name=data.name, activity_type=ActivityTypeEnum.TYPE_VIDEO, @@ -197,7 +204,7 @@ async def create_external_video_activity( "type": data.type, "activity_uuid": activity_uuid, }, - details=data.details, + details=details, version=1, creation_date=str(datetime.now()), update_date=str(datetime.now()), diff --git a/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/VideoActivityModal.tsx b/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/VideoActivityModal.tsx index 56e51f98..dfe737b8 100644 --- a/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/VideoActivityModal.tsx +++ b/apps/web/components/Objects/Modals/Activities/Create/NewActivityModal/VideoActivityModal.tsx @@ -1,11 +1,10 @@ -import FormLayout, { - ButtonBlack, - Flex, - FormField, - FormLabel, - FormMessage, - Input, -} from '@components/Objects/StyledElements/Form/Form' +import { + Button, +} from "@components/ui/button" +import { + Input +} from "@components/ui/input" +import { Label } from "@components/ui/label" import React, { useState } from 'react' import * as Form from '@radix-ui/react-form' import BarLoader from 'react-spinners/BarLoader' @@ -100,37 +99,35 @@ function VideoModal({

Video Settings

- - Start Time (seconds) - - setVideoDetails({ - ...videoDetails, - startTime: Math.max(0, parseInt(e.target.value) || 0) - })} - placeholder="0" - /> - - +
+ + setVideoDetails({ + ...videoDetails, + startTime: Math.max(0, parseInt(e.target.value) || 0) + })} + placeholder="0" + /> +
- - End Time (seconds, optional) - - setVideoDetails({ - ...videoDetails, - endTime: e.target.value ? parseInt(e.target.value) : null - })} - placeholder="Leave empty for full duration" - /> - - +
+ + setVideoDetails({ + ...videoDetails, + endTime: e.target.value ? parseInt(e.target.value) : null + })} + placeholder="Leave empty for full duration" + /> +
@@ -164,24 +161,18 @@ function VideoModal({ ) return ( - - - - Activity Name - - Please provide a name for your video activity - - - - setName(e.target.value)} - type="text" - required - placeholder="Enter activity name..." - /> - - + +
+ + setName(e.target.value)} + type="text" + required + placeholder="Enter activity name..." + /> +
@@ -214,10 +205,11 @@ function VideoModal({
{selectedView === 'file' && (
- - Video File +
+
- +
)} {selectedView === 'youtube' && (
- - YouTube URL - - setYoutubeUrl(e.target.value)} - type="text" - required - placeholder="https://youtube.com/watch?v=..." - /> - - +
+ + setYoutubeUrl(e.target.value)} + type="text" + required + placeholder="https://youtube.com/watch?v=..." + /> +
)}
- - - - {isSubmitting ? ( - - ) : ( - 'Create Activity' - )} - - - - +
+ +
+ ) } diff --git a/apps/web/services/courses/activities.ts b/apps/web/services/courses/activities.ts index e6209ff3..d8bdd017 100644 --- a/apps/web/services/courses/activities.ts +++ b/apps/web/services/courses/activities.ts @@ -75,14 +75,23 @@ export async function createExternalVideoActivity( data.chapter_id = chapter_id data.activity_id = activity.id - // Add video details if provided - data.details = { - startTime: data.startTime || 0, - endTime: data.endTime || null, - autoplay: data.autoplay || false, - muted: data.muted || false + // Add video details with null checking + const defaultDetails = { + startTime: 0, + endTime: null, + autoplay: false, + muted: false } + const videoDetails = data.details ? { + startTime: data.details.startTime ?? defaultDetails.startTime, + endTime: data.details.endTime ?? defaultDetails.endTime, + autoplay: data.details.autoplay ?? defaultDetails.autoplay, + muted: data.details.muted ?? defaultDetails.muted + } : defaultDetails + + data.details = JSON.stringify(videoDetails) + const result = await fetch( `${getAPIUrl()}activities/external_video`, RequestBodyWithAuthHeader('POST', data, null, access_token)