diff --git a/django_email_learning/public/views.py b/django_email_learning/public/views.py
index fe0f2470..1ad1943f 100644
--- a/django_email_learning/public/views.py
+++ b/django_email_learning/public/views.py
@@ -2,6 +2,8 @@
from django.db.models import Prefetch
from django_email_learning.models import Organization, Course
from django.http import Http404
+from django.urls import reverse
+from django.conf import settings
from django_email_learning.public.serializers import (
OrganizationSerializer,
PublicCourseSerializer,
@@ -47,6 +49,10 @@ def get_context_data(self, **kwargs) -> dict: # type: ignore[no-untyped-def]
description=organization.description,
courses=courses,
)
+ enroll_api_path = reverse("django_email_learning:api_public:enroll")
+ context[
+ "enroll_api_url"
+ ] = f"{settings.DJANGO_EMAIL_LEARNING['SITE_BASE_URL']}{enroll_api_path}"
context["organization_json"] = organization_data.model_dump_json()
context["organization"] = organization_data.model_dump()
context["page_title"] = organization.name
diff --git a/django_email_learning/templates/public/organization.html b/django_email_learning/templates/public/organization.html
index 8a76b2ee..692bb9b1 100644
--- a/django_email_learning/templates/public/organization.html
+++ b/django_email_learning/templates/public/organization.html
@@ -3,6 +3,7 @@
{% block head_script %}
{% vite_asset 'public/organization/Organization.jsx' %}
{% endblock %}
diff --git a/frontend/public/components/EnrollmentForm.jsx b/frontend/public/components/EnrollmentForm.jsx
index a19ca9fd..84f2c8d2 100644
--- a/frontend/public/components/EnrollmentForm.jsx
+++ b/frontend/public/components/EnrollmentForm.jsx
@@ -1,11 +1,14 @@
import React from 'react';
-import { Alert, Box, Button, Typography } from '@mui/material';
+import { Alert, Box, Button, CircularProgress, Typography } from '@mui/material';
import RequiredTextField from '../../src/components/RequiredTextField.jsx';
+import { getCookie } from '../../src/utils.js';
-const EnrollmentForm = ({course_title, course_slug, onCancel}) => {
+
+const EnrollmentForm = ({course_title, course_slug, organization_id, endpoint, onCancle, onComplete}) => {
const emailRef = React.useRef('');
const [errorMessage, setErrorMessage] = React.useState('');
+ const [isProcessing, setIsProcessing] = React.useState(false);
const validateForm = () => {
const email = emailRef.current.value;
@@ -25,13 +28,40 @@ const EnrollmentForm = ({course_title, course_slug, onCancel}) => {
const enroll = () => {
if (validateForm()) {
- // TODO: call the public API to enroll the user when the endpoint is ready
- console.log('Enrolling with email:', emailRef.current.value, 'for course:', course_slug);
+ setIsProcessing(true);
+ fetch(endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': getCookie('csrftoken'),
+ },
+ body: JSON.stringify({
+ email: emailRef.current.value,
+ course_slug: course_slug,
+ organization_id: organization_id,
+ }),
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.json();
+ })
+ .then(data => {
+ // Handle success
+ setIsProcessing(false);
+ onComplete();
+ })
+ .catch(error => {
+ setIsProcessing(false);
+ setErrorMessage('Enrollment failed. Please try again.');
+ });
}
}
- return (
+ return (
Enroll for {course_title}
+ { isProcessing ? : <>
{errorMessage && {errorMessage}}
{
if (e.key === 'Enter') {
@@ -40,13 +70,14 @@ const EnrollmentForm = ({course_title, course_slug, onCancel}) => {
}} />
-
+ >}
);
};
diff --git a/frontend/public/organization/Organization.jsx b/frontend/public/organization/Organization.jsx
index 3732bb30..8fb38336 100644
--- a/frontend/public/organization/Organization.jsx
+++ b/frontend/public/organization/Organization.jsx
@@ -1,21 +1,42 @@
-import { useState } from 'react'
+import { useState, useEffect } from 'react'
import render from '../../src/render.jsx';
import Layout from '../components/Layout.jsx';
import EnrollmentForm from '../components/EnrollmentForm.jsx';
-import { Box, Button, Dialog, Grid, Typography } from '@mui/material';
+import { Alert, Box, Button, Dialog, Grid, Typography } from '@mui/material';
function Organization() {
const [displayModal, setDisplayModal] = useState(false);
const [modalContent, setModalContent] = useState(null);
+ const [courses, setCourses] = useState([]);
+
+ useEffect(() => {
+ for (let course of organization.courses) {
+ course.enrolled = false;
+ }
+ setCourses(organization.courses);
+ }, []);
const showModalForCourse = (course) => {
// Logic to show modal for specific course
- setModalContent( {setDisplayModal(false); setModalContent(null);}} />);
+ setModalContent( {setDisplayModal(false); setModalContent(null);}} onComplete={() => completeEnrollment(course)} />);
setDisplayModal(true);
}
+ const completeEnrollment = (course) => {
+ setDisplayModal(false);
+ setModalContent(null);
+ // Disable the enrolled course button
+ let updatedCourses = courses.map(c => {
+ if (c.id === course.id) {
+ return { ...c, enrolled: true };
+ }
+ return c;
+ });
+ setCourses(updatedCourses);
+ }
+
return
{ organization.logo_url &&
@@ -27,14 +48,15 @@ function Organization() {
Courses:
{ organization.courses.length > 0 ? (
- { organization.courses.map((course) => (
+ { courses.map((course) => (
{course.title}
- showModalForCourse(course)}>
+ showModalForCourse(course)} disabled={course.enrolled}>
Enroll Now
+ { course.enrolled && You are enrolled in this course. }
))}
diff --git a/tests/public/test_views/test_public_organization_view.py b/tests/public/test_views/test_public_organization_view.py
index 8dfe55e4..66ea8753 100644
--- a/tests/public/test_views/test_public_organization_view.py
+++ b/tests/public/test_views/test_public_organization_view.py
@@ -9,6 +9,7 @@ def test_organization_view_anonymous_client(db, anonymous_client):
assert response.status_code == 200
assert response.context["organization"]["id"] == 1
assert response.context["page_title"] == response.context["organization"]["name"]
+ assert response.context["enroll_api_url"].startswith("http")
assert "organization_json" in response.context
# No course added yet, so courses list should be empty