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() {
-
-
- - -
-
+ + + + + +
+
@@ -252,7 +279,7 @@ export default function Pricing() { animate={controls} className="d-inline-block" > - {basicPrice} + {"$" + basicPrice} {" "} / student / course @@ -265,7 +292,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"