Skip to content

Commit

Permalink
Merge pull request #71 from abinth11/bug-fix-enroll-student
Browse files Browse the repository at this point in the history
fixed course enrollment
  • Loading branch information
abinth11 authored Aug 12, 2023
2 parents fd9151f + f3b6d84 commit 4d8c598
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, Fragment } from "react";
import React, { useState, useEffect, Fragment } from "react";
import {
Button,
Dialog,
Expand All @@ -9,7 +9,7 @@ import {
} from "@material-tailwind/react";
import { useNavigate, useParams } from "react-router-dom";
import { ExclamationCircleIcon } from "@heroicons/react/24/outline";
import { formatToINR } from "../../../utils/helpers";
import { formatToINR, formatTime } from "../../../utils/helpers";
import { enrollStudent } from "../../../api/endpoints/course/course";
import { toast } from "react-toastify";
import { FaSpinner } from "react-icons/fa";
Expand All @@ -35,7 +35,11 @@ const PaymentConfirmationModal: React.FC<PaymentModalProps> = ({
const { courseId } = useParams();
const [isLoading, setIsLoading] = useState<boolean>(false);
const navigate = useNavigate();
const handleClose = () => setOpen(false);
const handleClose = () => setOpen(false);
const offerExpiration = "2023-08-13T22:59:59.000Z";

const [timeLeft, setTimeLeft] = useState<number>(0);

const handleConfirmPayment = async () => {
try {
setIsLoading(true);
Expand Down Expand Up @@ -64,57 +68,94 @@ const PaymentConfirmationModal: React.FC<PaymentModalProps> = ({
}
};
const isFreeCourse = courseDetails?.isPaid === false;

useEffect(() => {
if (!isFreeCourse) {
const offerEndTime = new Date(offerExpiration).getTime();
const currentTime = new Date().getTime();

const timeRemaining = offerEndTime - currentTime;
setTimeLeft(timeRemaining);

const timer = setInterval(() => {
setTimeLeft((prevTime) => (prevTime > 1000 ? prevTime - 1000 : 0));
}, 1000);

return () => {
clearInterval(timer);
};
}
}, [isFreeCourse, offerExpiration]);

return (
<Fragment>
<Dialog open={open} size='sm' handler={handleOpen}>
<DialogHeader>
<div className='flex items-center justify-center'>
<ExclamationCircleIcon className='h-8 w-8 text-yellow-500' />
<Typography variant='h5' color='gray' className='ml-2'>
Payment Confirmation
<Typography
variant='h5'
color='gray'
className='ml-2 font-semibold'
>
{isFreeCourse
? "Explore Your Free Learning Adventure"
: "Payment Confirmation"}
</Typography>
</div>
</DialogHeader>
<DialogBody divider>
<Typography variant='body' color='gray'>
<Typography variant='body' className='font-semibold' color='gray'>
Please review the details before proceeding:
</Typography>
<Typography variant='body' color='gray' className='mt-4'>
{isFreeCourse
? "This course is free!"
: `Course Price: ${formatToINR(courseDetails?.price)}`}
<Typography variant='body' color='gray' className='mt-2 mb-1'>
{isFreeCourse ? (
<span className='font-semibold text-green-500'>
This course is free!
</span>
) : (
<div className="bg-gray-100 p-2">
<p className="text-lg font-semibold mb-2">🎉 Limited Time Offer 🎉</p>
<p className="text-xl font-bold mb-2">
Price:{" "}
<span className="text-green-600">
{formatToINR(courseDetails?.price)}
</span>{" "}
<span className="text-gray-600 line-through">
{formatToINR(courseDetails?.price + 100)}
</span>
</p>
<p className="text-lg">
Offer Expires in:
<span className="text-gray-600 font-semibold">
{formatTime(timeLeft)}
</span>
</p>
</div>

)}
</Typography>
<Typography variant='body' color='gray'>
Course Overview: {courseDetails?.overview}
<span className='font-semibold'>Course Overview:</span>{" "}
{courseDetails?.overview}
</Typography>
</DialogBody>
<DialogFooter>
{isFreeCourse ? (
<Button
<Button
variant='gradient'
color='green'
onClick={handleCourseEnroll}
className='w-full flex items-center justify-center'
className='w-full'
>
{isLoading ? (
<span className="flex items-center">
<span>Loading</span>
<span className='flex items-center'>
<span>Processing...</span>
<FaSpinner className='animate-spin ml-1' size={20} />
</span>
) : (
<span>Start Course</span>
<span>{isFreeCourse ? "Start Course" : "Confirm Payment"}</span>
)}
</Button>
) : (
<Button
variant='gradient'
color='green'
onClick={handleCourseEnroll}
className='w-full'
>
<span>Confirm Payment</span>
</Button>
)}
<Button
variant='outlined'
color='blue'
Expand Down
2 changes: 1 addition & 1 deletion client/src/redux/reducers/instructorSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const decodedToken = decodeJwtToken(accessToken ?? "");

const initialState: InstructorData = {
instructorDetails: null,
instructorId: decodedToken?.payload.userId || null,
instructorId: decodedToken?.payload.Id || null,
};

const instructorSlice = createSlice({
Expand Down
3 changes: 1 addition & 2 deletions client/src/redux/reducers/studentSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ interface StudentData {
const accessToken = localStorage.getItem("accessToken");
const decodedToken = decodeJwtToken(accessToken??"")


const initialState: StudentData = {
studentDetails: null,
studentId: decodedToken?.payload.userId || null,
studentId: decodedToken?.payload.Id || null,
isFetching: false,
error: null,
};
Expand Down
2 changes: 1 addition & 1 deletion client/src/utils/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import jwtDecode, { JwtPayload } from "jwt-decode";

interface Payload {
email: string;
userId: string;
Id: string;
role: string;
}
interface DecodedToken extends JwtPayload {
Expand Down
11 changes: 11 additions & 0 deletions client/src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ export function formatToINR(number: number): string {
return formatter.format(number);
}

export const formatTime = (milliseconds: number): string => {
const seconds = Math.floor(milliseconds / 1000);
const days = Math.floor(seconds / 86400); // 86400 seconds in a day
const hours = Math.floor((seconds % 86400) / 3600); // 3600 seconds in an hour
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;

const formattedTime = `${days}d ${hours}h ${minutes}m ${remainingSeconds}s`;
return formattedTime;
};

18 changes: 11 additions & 7 deletions server/src/app/usecases/course/enroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PaymentInfo } from '@src/types/payment';
export const enrollStudentU = async (
courseId: string,
studentId: string,
paymentInfo: PaymentInfo,
paymentInfo: any,
courseDbRepository: ReturnType<CourseDbRepositoryInterface>,
paymentDbRepository: ReturnType<PaymentInterface>
) => {
Expand All @@ -25,14 +25,18 @@ export const enrollStudentU = async (
}
const course = await courseDbRepository.getCourseById(courseId);
if (course?.isPaid) {
const paymentId = paymentInfo.id;
const amount = paymentInfo.amount / 100;
paymentInfo.courseId = courseId;
paymentInfo.paymentId = paymentId;
paymentInfo.amount = amount;
const payment = {
paymentId: paymentInfo.id,
courseId: courseId,
studentId: studentId,
amount: paymentInfo.amount / 100,
currency: paymentInfo.currency,
payment_method: paymentInfo.payment_method,
status: paymentInfo.status
};
await Promise.all([
courseDbRepository.enrollStudent(courseId, studentId),
paymentDbRepository.savePayment(paymentInfo)
paymentDbRepository.savePayment(payment)
]);
} else {
await courseDbRepository.enrollStudent(courseId, studentId);
Expand Down
2 changes: 2 additions & 0 deletions server/src/frameworks/database/mongodb/models/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import mongoose, { Schema, Document } from 'mongoose';

interface PaymentI extends Document {
paymentId: string;
studentId:string;
courseId: string;
amount: number;
currency: string;
Expand All @@ -12,6 +13,7 @@ interface PaymentI extends Document {

const paymentSchema: Schema<PaymentI> = new Schema({
paymentId: { type: String, required: true },
studentId:{ type: String, required: true },
courseId: { type: String, required: true },
amount: { type: Number, required: true },
currency: { type: String, required: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const paymentRepositoryMongodb = () => {
};

const getMonthlyRevenue = async () => {
const currentMonth = new Date().getMonth() + 1; // Get the current month (1-based index)
const currentMonth = new Date().getMonth() + 1;
const pipeline = [
{
$match: {
Expand Down
25 changes: 1 addition & 24 deletions server/src/types/payment.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,10 @@
export interface PaymentInfo {
id: string;
courseId:string;
studentId:string;
paymentId?:string;
object: string;
amount: number;
amount_details: {
// Specify the structure of amount_details if needed
};
automatic_payment_methods: {
// Specify the structure of automatic_payment_methods if needed
};
canceled_at: string | null;
cancellation_reason: string | null;
capture_method: string;
client_secret: string;
confirmation_method: string;
created: number;
currency: string;
description: string | null;
last_payment_error: string | null;
livemode: boolean;
next_action: string | null;
payment_method: string;
payment_method_types: string[];
processing: string | null;
receipt_email: string | null;
setup_future_usage: string | null;
shipping: string | null;
source: string | null;
status: string;
}

0 comments on commit 4d8c598

Please sign in to comment.