From ae0fa0fb176f5a2c648ef99870ed402fce65cc20 Mon Sep 17 00:00:00 2001
From: Nathan Sarang-Walters
Date: Wed, 11 Dec 2024 10:44:57 -0800
Subject: [PATCH] Update pricing for Fall 2024 (#84)
* Update pricing for Fall 2024
* Use academic calendar instead of term system
* Use correct term
* Don't show student-paid pricing for non-semester terms
* Use fixed pricing for student-pays
* Only animate prices when they change
* Fix lint
---
package.json | 1 +
src/lib/useUpdateEffect.ts | 14 ++++
src/pages/pricing/index.tsx | 133 ++++++++++++++++++++++--------------
yarn.lock | 5 ++
4 files changed, 100 insertions(+), 53 deletions(-)
create mode 100644 src/lib/useUpdateEffect.ts
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/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 cc333a07..82da139e 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";
@@ -11,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 = [
{
@@ -78,12 +83,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.
-
),
},
@@ -134,21 +133,55 @@ function ContactUsButton({ className }: { className?: string }) {
);
}
+type AcademicCalendar = "semester" | "quarter" | "monthly";
+type PaymentModel = "course" | "student";
+
export default function Pricing() {
const controls = useAnimationControls();
- const [paymentModel, setPaymentModel] = React.useState<"course" | "student">(
- "course"
- );
+
+ const [academicCalendar, setAcademicCalendar] =
+ React.useState("semester");
+
+ const [paymentModel, setPaymentModel] =
+ React.useState("course");
+
const [showModal, setShowModal] = React.useState(false);
- const basicPrice = paymentModel === "course" ? "$6" : "$10";
- const premiumPrice = paymentModel === "course" ? "$12" : "$16";
+ let basicPrice = run(() => {
+ if (academicCalendar === "semester") {
+ return 8;
+ } else if (academicCalendar === "quarter") {
+ return 6;
+ } else {
+ return 2;
+ }
+ });
- const updatePaymentModel = (model: "course" | "student") => {
- setPaymentModel(model);
- controls.start({ scale: 1.3 }).then(() => controls.start({ scale: 1 }));
+ 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 = 10;
+ premiumPrice = 18;
+ }
+
+ const updatePaymentModel = (paymentModel: "course" | "student") => {
+ setPaymentModel(paymentModel);
+ };
+
+ const updateAcademicCalendar = (
+ academicCalendar: "semester" | "quarter" | "monthly"
+ ) => {
+ setAcademicCalendar(academicCalendar);
};
+ // 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,
@@ -187,46 +220,40 @@ export default function Pricing() {