From 48acb7ace994be5b86eaf1e5832714760cdfe1f0 Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Tue, 10 Dec 2024 16:33:44 -0800
Subject: [PATCH 1/7] Update pricing for Fall 2024
---
package.json | 1 +
src/pages/pricing/index.tsx | 110 ++++++++++++++++++++----------------
yarn.lock | 5 ++
3 files changed, 67 insertions(+), 49 deletions(-)
diff --git a/package.json b/package.json
index 334491a9..77e79403 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@mdx-js/loader": "^2.3.0",
"@mdx-js/react": "^2.3.0",
"@next/mdx": "^13.2.4",
+ "@prairielearn/run": "^1.0.2",
"bootstrap": "^5.2.3",
"bootstrap-icons": "^1.11.1",
"classnames": "^2.3.1",
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index cc333a07..5fbe4f41 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -2,8 +2,12 @@ import React from "react";
import Head from "next/head";
import Link from "next/link";
import Accordion from "react-bootstrap/Accordion";
+import Col from "react-bootstrap/Col";
+import Form from "react-bootstrap/Form";
+import Row from "react-bootstrap/Row";
import classnames from "classnames";
import { motion, useAnimationControls } from "framer-motion";
+import { run } from "@prairielearn/run";
import CheckIcon from "../../components/CheckIcon";
import Stack from "../../components/Stack";
@@ -78,12 +82,6 @@ const FAQS = [
Students will be responsible for paying the PrairieLearn fee before
they are able to access any of your course's content.
-
- This pricing model is currently in development, and it is expected to
- be available by Fall 2023. If your course would like to be any early
- adopter of this payment model, please{" "}
- contact us.
-
),
},
@@ -136,16 +134,38 @@ function ContactUsButton({ className }: { className?: string }) {
export default function Pricing() {
const controls = useAnimationControls();
+ const [termSystem, setTermSystem] = React.useState<
+ "semester" | "quarter" | "monthly"
+ >("semester");
const [paymentModel, setPaymentModel] = React.useState<"course" | "student">(
"course"
);
const [showModal, setShowModal] = React.useState(false);
- const basicPrice = paymentModel === "course" ? "$6" : "$10";
- const premiumPrice = paymentModel === "course" ? "$12" : "$16";
+ let basicPrice = run(() => {
+ if (termSystem === "semester") {
+ return 8;
+ } else if (termSystem === "quarter") {
+ return 6;
+ } else {
+ return 2;
+ }
+ });
+
+ let premiumPrice = basicPrice * 2;
+
+ if (paymentModel === "student") {
+ basicPrice += 2;
+ premiumPrice += 2;
+ }
+
+ const updatePaymentModel = (paymentModel: "course" | "student") => {
+ setPaymentModel(paymentModel);
+ controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
+ };
- const updatePaymentModel = (model: "course" | "student") => {
- setPaymentModel(model);
+ const updateTermSystem = (termSystem: "semester" | "quarter" | "monthly") => {
+ setTermSystem(termSystem);
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
@@ -187,46 +207,38 @@ export default function Pricing() {
-
-
- updatePaymentModel("course")}
- >
-
+
+
+ Term system
+ {
+ updateTermSystem(e.currentTarget.value as any);
+ }}
>
- ✓
-
- Course pays
-
- updatePaymentModel("student")}
- >
- Semester
+ Quarter
+ Monthly
+
+
+
+
+
+
+ Payment model
+ {
+ updatePaymentModel(e.currentTarget.value as any);
+ }}
>
- ✓
-
- Student pays
-
-
-
+
Institution or course pays
+
Student pays
+
+
+
+
+
@@ -252,7 +264,7 @@ export default function Pricing() {
animate={controls}
className="d-inline-block"
>
- {basicPrice}
+ {"$" + basicPrice}
{" "}
/ student / course
@@ -265,7 +277,7 @@ export default function Pricing() {
animate={controls}
className="d-inline-block"
>
- {premiumPrice}
+ {"$" + premiumPrice}
{" "}
/ student / course
diff --git a/yarn.lock b/yarn.lock
index f146aa15..c7908fc2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -241,6 +241,11 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45"
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
+"@prairielearn/run@^1.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@prairielearn/run/-/run-1.0.2.tgz#86d867d74a09d1e9b32a39d73b5ded5d1b11bf55"
+ integrity sha512-V8H90u3FSNTNu26oxyTx1q3PvyLge/5nbCwZQomxXnpAsfp/dV+W344M1dIk54LKRC4otbLW3QjrErK3/Uqx0Q==
+
"@react-aria/ssr@^3.2.0":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.4.1.tgz#79e8bb621487e8f52890c917d3c734f448ba95e7"
From 7129fb56619ce8d0525ed1fc2be0bf7bc0dc7791 Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 09:27:52 -0800
Subject: [PATCH 2/7] Use academic calendar instead of term system
---
src/pages/pricing/index.tsx | 32 +++++++++++++++++++-------------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index 5fbe4f41..d334a8f3 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -132,20 +132,24 @@ function ContactUsButton({ className }: { className?: string }) {
);
}
+type AcademicSchedule = "semester" | "quarter" | "monthly";
+type PaymentModel = "course" | "student";
+
export default function Pricing() {
const controls = useAnimationControls();
- const [termSystem, setTermSystem] = React.useState<
- "semester" | "quarter" | "monthly"
- >("semester");
- const [paymentModel, setPaymentModel] = React.useState<"course" | "student">(
- "course"
- );
+
+ const [academicSchedule, setAcademicSchedule] =
+ React.useState("semester");
+
+ const [paymentModel, setPaymentModel] =
+ React.useState("course");
+
const [showModal, setShowModal] = React.useState(false);
let basicPrice = run(() => {
- if (termSystem === "semester") {
+ if (academicSchedule === "semester") {
return 8;
- } else if (termSystem === "quarter") {
+ } else if (academicSchedule === "quarter") {
return 6;
} else {
return 2;
@@ -164,8 +168,10 @@ export default function Pricing() {
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
- const updateTermSystem = (termSystem: "semester" | "quarter" | "monthly") => {
- setTermSystem(termSystem);
+ const updateAcademicSchedule = (
+ academicSchedule: "semester" | "quarter" | "monthly"
+ ) => {
+ setAcademicSchedule(academicSchedule);
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
@@ -209,11 +215,11 @@ export default function Pricing() {
-
- Term system
+
+ Academic schedule
{
- updateTermSystem(e.currentTarget.value as any);
+ updateAcademicSchedule(e.currentTarget.value as any);
}}
>
Semester
From 56ec012d27d6ba77938bff4d871a1a9ef50a8b0f Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 09:31:17 -0800
Subject: [PATCH 3/7] Use correct term
---
src/pages/pricing/index.tsx | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index d334a8f3..16aedcb4 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -132,14 +132,14 @@ function ContactUsButton({ className }: { className?: string }) {
);
}
-type AcademicSchedule = "semester" | "quarter" | "monthly";
+type AcademicCalendar = "semester" | "quarter" | "monthly";
type PaymentModel = "course" | "student";
export default function Pricing() {
const controls = useAnimationControls();
- const [academicSchedule, setAcademicSchedule] =
- React.useState("semester");
+ const [academicCalendar, setAcademicCalendar] =
+ React.useState("semester");
const [paymentModel, setPaymentModel] =
React.useState("course");
@@ -147,9 +147,9 @@ export default function Pricing() {
const [showModal, setShowModal] = React.useState(false);
let basicPrice = run(() => {
- if (academicSchedule === "semester") {
+ if (academicCalendar === "semester") {
return 8;
- } else if (academicSchedule === "quarter") {
+ } else if (academicCalendar === "quarter") {
return 6;
} else {
return 2;
@@ -168,10 +168,10 @@ export default function Pricing() {
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
- const updateAcademicSchedule = (
- academicSchedule: "semester" | "quarter" | "monthly"
+ const updateAcademicCalendar = (
+ academicCalendar: "semester" | "quarter" | "monthly"
) => {
- setAcademicSchedule(academicSchedule);
+ setAcademicCalendar(academicCalendar);
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
@@ -215,11 +215,11 @@ export default function Pricing() {
-
- Academic schedule
+
+ Academic calendar
{
- updateAcademicSchedule(e.currentTarget.value as any);
+ updateAcademicCalendar(e.currentTarget.value as any);
}}
>
Semester
From 409a65bd7badfbcf0bd9211351dea59c0f42da53 Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 09:43:02 -0800
Subject: [PATCH 4/7] Don't show student-paid pricing for non-semester terms
---
src/pages/pricing/index.tsx | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index 16aedcb4..9edf6a7f 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -82,6 +82,12 @@ const FAQS = [
Students will be responsible for paying the PrairieLearn fee before
they are able to access any of your course's content.
+
+ Note that we don't currently support the student-paid model for
+ terms shorter than a semester. If you're interested in using
+ PrairieLearn for a shorter term, please{" "}
+ contact us.
+
),
},
@@ -165,6 +171,13 @@ export default function Pricing() {
const updatePaymentModel = (paymentModel: "course" | "student") => {
setPaymentModel(paymentModel);
+
+ if (paymentModel === "student") {
+ // We don't currently support shorter terms for student-paid pricing.
+ // We'll force it to "semester" and disable the select below.
+ setAcademicCalendar("semester");
+ }
+
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
@@ -214,10 +227,12 @@ export default function Pricing() {
-
+
Academic calendar
{
updateAcademicCalendar(e.currentTarget.value as any);
}}
@@ -229,10 +244,11 @@ export default function Pricing() {
-
+
Payment model
{
updatePaymentModel(e.currentTarget.value as any);
}}
From c3ca15a4b3139455888782464a8732afc0248bfb Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 09:58:03 -0800
Subject: [PATCH 5/7] Use fixed pricing for student-pays
---
src/pages/pricing/index.tsx | 21 +++++----------------
1 file changed, 5 insertions(+), 16 deletions(-)
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index 9edf6a7f..c8837c32 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -82,12 +82,6 @@ const FAQS = [
Students will be responsible for paying the PrairieLearn fee before
they are able to access any of your course's content.
-
- Note that we don't currently support the student-paid model for
- terms shorter than a semester. If you're interested in using
- PrairieLearn for a shorter term, please{" "}
- contact us.
-
),
},
@@ -164,20 +158,16 @@ export default function Pricing() {
let premiumPrice = basicPrice * 2;
+ // We don't currently have the ability to set the price for students based
+ // on the length of the term, so we just hardcode a single fixed price that
+ // assumes a semester-length term.
if (paymentModel === "student") {
- basicPrice += 2;
- premiumPrice += 2;
+ basicPrice = 10;
+ premiumPrice = 18;
}
const updatePaymentModel = (paymentModel: "course" | "student") => {
setPaymentModel(paymentModel);
-
- if (paymentModel === "student") {
- // We don't currently support shorter terms for student-paid pricing.
- // We'll force it to "semester" and disable the select below.
- setAcademicCalendar("semester");
- }
-
controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
@@ -232,7 +222,6 @@ export default function Pricing() {
Academic calendar
{
updateAcademicCalendar(e.currentTarget.value as any);
}}
From 82f6905b4a4bb1f575179a2acdcdf0a19feaecb1 Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 10:24:23 -0800
Subject: [PATCH 6/7] Only animate prices when they change
---
src/lib/useUpdateEffect.ts | 14 ++++++++++++++
src/pages/pricing/index.tsx | 10 +++++++---
2 files changed, 21 insertions(+), 3 deletions(-)
create mode 100644 src/lib/useUpdateEffect.ts
diff --git a/src/lib/useUpdateEffect.ts b/src/lib/useUpdateEffect.ts
new file mode 100644
index 00000000..6da75a12
--- /dev/null
+++ b/src/lib/useUpdateEffect.ts
@@ -0,0 +1,14 @@
+import { useEffect, useRef } from "react";
+
+export function useUpdateEffect(effect: () => void, deps: any[]) {
+ const isMounted = useRef(false);
+
+ useEffect(() => {
+ if (!isMounted.current) {
+ isMounted.current = true;
+ } else {
+ return effect();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, deps);
+}
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index c8837c32..80f60505 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useEffect } from "react";
import Head from "next/head";
import Link from "next/link";
import Accordion from "react-bootstrap/Accordion";
@@ -15,6 +15,7 @@ import { PageBanner } from "../../components/Banner";
import styles from "./index.module.scss";
import { RequestCourseModal } from "../../components/RequestCourseModal";
+import { useUpdateEffect } from "../../lib/useUpdateEffect";
const FEATURES = [
{
@@ -168,16 +169,19 @@ export default function Pricing() {
const updatePaymentModel = (paymentModel: "course" | "student") => {
setPaymentModel(paymentModel);
- controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
const updateAcademicCalendar = (
academicCalendar: "semester" | "quarter" | "monthly"
) => {
setAcademicCalendar(academicCalendar);
- controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
};
+ // Only animate the price change if the price actually changes.
+ useUpdateEffect(() => {
+ controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
+ }, [premiumPrice, basicPrice]);
+
function RequestCourseButton({
text,
className,
From b1765c26a64d109c55cad0ed923fe24eb138fa47 Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 10:28:09 -0800
Subject: [PATCH 7/7] Fix lint
---
src/pages/pricing/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx
index 80f60505..82da139e 100644
--- a/src/pages/pricing/index.tsx
+++ b/src/pages/pricing/index.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect } from "react";
+import React from "react";
import Head from "next/head";
import Link from "next/link";
import Accordion from "react-bootstrap/Accordion";