Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/course/configuration/student-pay.tsx
Views: 687
import {1Alert,2Button,3Card,4Checkbox,5DatePicker,6Divider,7Space,8Spin,9} from "antd";10import dayjs from "dayjs";11import { isEqual } from "lodash";12import { useEffect, useMemo, useState } from "react";13import { FormattedMessage, useIntl } from "react-intl";1415import { Gap, Icon, TimeAgo } from "@cocalc/frontend/components";16import { labels } from "@cocalc/frontend/i18n";17import LicenseEditor from "@cocalc/frontend/purchases/license-editor";18import MoneyStatistic from "@cocalc/frontend/purchases/money-statistic";19import { webapp_client } from "@cocalc/frontend/webapp-client";20import { compute_cost } from "@cocalc/util/licenses/purchase/compute-cost";21import { DEFAULT_PURCHASE_INFO } from "@cocalc/util/licenses/purchase/student-pay";22import type { PurchaseInfo } from "@cocalc/util/licenses/purchase/types";23import { currency } from "@cocalc/util/misc";2425export default function StudentPay({ actions, settings }) {26const intl = useIntl();2728const [minPayment, setMinPayment] = useState<number | undefined>(undefined);29const updateMinPayment = () => {30(async () => {31setMinPayment(await webapp_client.purchases_client.getMinimumPayment());32})();33};34useEffect(() => {35updateMinPayment();36}, []);3738const [info, setInfo] = useState<PurchaseInfo>(() => {39const cur = settings.get("payInfo")?.toJS();40if (cur != null) {41return cur;42}43const info = {44...DEFAULT_PURCHASE_INFO,45start: new Date(),46end: dayjs().add(3, "month").toDate(),47} as PurchaseInfo;48actions.configuration.setStudentPay({ info, cost });49return info;50});5152if (info.type == "vouchers") {53// for typescript54throw Error("bug");55}5657const getWhenFromSettings = () => {58const pay = settings.get("pay");59if (pay) {60return dayjs(pay);61}62if (info.start) {63return dayjs(info.start).add(7, "day");64}65return dayjs().add(7, "day");66};6768const [when, setWhen] = useState<dayjs.Dayjs>(getWhenFromSettings);69const cost = useMemo(() => {70try {71return compute_cost(info).cost;72} catch (_) {73return null;74}75}, [info]);7677const [showStudentPay, setShowStudentPay] = useState<boolean>(false);78const reset = () => {79const cur = settings.get("payInfo")?.toJS();80if (cur != null) {81setInfo(cur);82}83setWhen(getWhenFromSettings());84};8586useEffect(() => {87// whenever opening the panel to edit, set controls to what is in the store.88if (showStudentPay) {89reset();90}91}, [showStudentPay]);9293useEffect(() => {94// this makes it sync with any other editor when closed.95if (!showStudentPay) {96reset();97}98}, [settings.get("payInfo")]);99100const paySelected = useMemo(() => {101if (!settings) return false;102return settings.get("student_pay") || settings.get("institute_pay");103}, [settings]);104105if (settings == null || actions == null) {106return <Spin />;107}108109const buttons = showStudentPay ? (110<Space style={{ margin: "10px 0", float: "right" }}>111<Button112onClick={() => {113setShowStudentPay(false);114reset();115}}116>117{intl.formatMessage(labels.cancel)}118</Button>119<Button120disabled={121isEqual(info, settings.get("payInfo")?.toJS()) &&122when.isSame(dayjs(settings.get("pay")))123}124type="primary"125onClick={() => {126actions.configuration.setStudentPay({ info, when, cost });127setShowStudentPay(false);128}}129>130{intl.formatMessage(labels.save_changes)}131</Button>132</Space>133) : undefined;134135return (136<Card137style={!paySelected ? { background: "#fcf8e3" } : undefined}138title={139<>140<Icon name="dashboard" />{" "}141<FormattedMessage142id="course.student-pay.title"143defaultMessage={"Require Students to Upgrade (Students Pay)"}144/>145</>146}147>148{cost != null && !showStudentPay && !!settings?.get("student_pay") && (149<div style={{ float: "right" }}>150<MoneyStatistic title="Cost Per Student" value={cost} />151</div>152)}153<Checkbox154checked={!!settings?.get("student_pay")}155onChange={(e) => {156actions.configuration.set_pay_choice("student", e.target.checked);157if (e.target.checked) {158setShowStudentPay(true);159actions.configuration.setStudentPay({160when: getWhenFromSettings(),161info,162cost,163});164actions.configuration.configure_all_projects();165}166}}167>168<FormattedMessage169id="course.student-pay.checkbox.students-pay"170defaultMessage={"Students pay directly"}171/>172</Checkbox>173{settings?.get("student_pay") && (174<div>175{buttons}176<Space style={{ margin: "10px 0" }}>177<Button178disabled={showStudentPay}179onClick={() => {180setShowStudentPay(true);181}}182>183<Icon name="credit-card" /> Start and end dates and upgrades...184</Button>185</Space>186<div>187{showStudentPay && (188<Alert189style={{ margin: "15px 0" }}190message={191<>192<Icon name="credit-card" /> Require Students to Upgrade193their Project194</>195}196description={197<div>198The cost is determined by the course length and desired199upgrades, which you configure below:200<div201style={{202height: "65px",203textAlign: "center",204}}205>206{cost != null && (207<MoneyStatistic title="Cost" value={cost} />208)}209</div>210<Divider>Configuration</Divider>211<LicenseEditor212noCancel213cellStyle={{ padding: 0, margin: "-10px 0" }}214info={info}215onChange={setInfo}216hiddenFields={new Set(["quantity", "custom_member"])}217/>218<div style={{ margin: "15px 0" }}>219<StudentPayCheckboxLabel220settings={settings}221when={when}222/>223</div>224{!!settings.get("pay") && (225<RequireStudentsPayWhen226when={when}227setWhen={setWhen}228cost={cost}229minPayment={minPayment}230info={info}231/>232)}233{buttons}234</div>235}236/>237)}238<hr />239<div style={{ color: "#666" }}>240<StudentPayDesc241settings={settings}242when={when}243cost={cost}244minPayment={minPayment}245/>246</div>247</div>248</div>249)}250</Card>251);252}253254function StudentPayCheckboxLabel({ settings, when }) {255if (settings.get("pay")) {256if (webapp_client.server_time() >= settings.get("pay")) {257return <span>Require that students upgrade immediately:</span>;258} else {259return (260<span>261Require that students upgrade by <TimeAgo date={when} />:{" "}262</span>263);264}265} else {266return <span>Require that students upgrade...</span>;267}268}269270function RequireStudentsPayWhen({ when, setWhen, cost, minPayment, info }) {271const start = dayjs(info.start);272return (273<div style={{ marginBottom: "15px" }}>274<div style={{ textAlign: "center", marginBottom: "15px" }}>275<DatePicker276changeOnBlur277showToday278allowClear={false}279disabledDate={(current) =>280current < start.subtract(1, "day") ||281current >= start.add(21, "day")282}283defaultValue={when}284onChange={(date) => {285setWhen(date ?? dayjs());286}}287/>288</div>289<RequireStudentPayDesc cost={cost} when={when} minPayment={minPayment} />290</div>291);292}293294function StudentPayDesc({ settings, cost, when, minPayment }) {295if (settings.get("pay")) {296return (297<span>298<span style={{ fontSize: "18pt" }}>299<Icon name="check" />300</span>{" "}301<Gap />302<RequireStudentPayDesc303cost={cost}304when={when}305minPayment={minPayment}306/>307</span>308);309} else {310return (311<span>312Require that all students in the course pay a one-time fee to upgrade313their project. This is strongly recommended, and ensures that your314students have a much better experience, and do not see a large{" "}315<span316style={{ color: "white", background: "darkred", padding: "0 5px" }}317>318RED warning banner319</span>{" "}320all the time. Alternatively, you (or your university) can pay for all321students -- see below.322</span>323);324}325}326327function RequireStudentPayDesc({ cost, when, minPayment }) {328if (when > dayjs()) {329return (330<span>331<b>332Your students will see a warning until <TimeAgo date={when} />.333</b>{" "}334{cost != null && (335<>336They will then be required to upgrade for a{" "}337<b>one-time fee of {currency(cost)}</b>. This cost in USD is locked338in, even if the rates on our site change.{" "}339{minPayment != null && cost < minPayment340? `NOTE: Students will have341to pay ${currency(342minPayment,343)} since that is the minimum transaction; they can use excess credit for other purchases.`344: ""}345</>346)}347</span>348);349} else {350return (351<span>352<b>353Your students are required to upgrade their project now to use it.354</b>{" "}355If you want to give them more time to upgrade, move the date forward.356</span>357);358}359}360361362