Path: blob/master/src/packages/next/pages/redeem.tsx
1447 views
/*1* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { useState } from "react";6import Footer from "components/landing/footer";7import Header from "components/landing/header";8import Head from "components/landing/head";9import { Alert, Button, Card, Divider, Input, Layout, Space } from "antd";10import withCustomize from "lib/with-customize";11import { Customize } from "lib/customize";12import { Icon } from "@cocalc/frontend/components/icon";13import A from "components/misc/A";14import InPlaceSignInOrUp from "components/auth/in-place-sign-in-or-up";15import useProfile from "lib/hooks/profile";16import { useRouter } from "next/router";17import apiPost from "lib/api/post";18import useIsMounted from "lib/hooks/mounted";19import Loading from "components/share/loading";20import type { CreatedItem } from "@cocalc/server/vouchers/redeem";21import { currency } from "@cocalc/util/misc";2223type State = "input" | "redeeming" | "redeemed";2425interface Props {26customize;27id?: string;28}2930export default function Redeem({ customize, id }: Props) {31const isMounted = useIsMounted();32const [code, setCode] = useState<string>(id ?? "");33const [error, setError] = useState<string>("");34const [state, setState] = useState<State>("input");35const profile = useProfile({ noCache: true });36const [signedIn, setSignedIn] = useState<boolean>(!!profile?.account_id);37const router = useRouter();38const [createdItems, setCreatedItems] = useState<CreatedItem[] | null>(null);3940async function redeemCode() {41try {42setError("");43setState("redeeming");44// This api call tells the backend, "create requested vouchers from everything in my45// shopping cart that is not a subscription."46const v = code.split("/");47const c = v[v.length - 1]?.trim();48const createdItems = await apiPost("/vouchers/redeem", {49code: c,50});51if (!isMounted.current) return;52setCreatedItems(createdItems);53// success!54setState("redeemed");55} catch (err) {56// The redeem failed.57setError(err.message);58setState("input"); // back to input mode59} finally {60if (!isMounted.current) return;61}62}6364return (65<Customize value={customize}>66<Head title="Redeem Voucher" />67<Layout>68<Header />69<Layout.Content70style={{71backgroundColor: "white",72}}73>74<div75style={{76width: "100%",77margin: "10vh 0",78display: "flex",79justifyContent: "center",80}}81>82{profile == null && <Loading />}83{profile != null && !profile.account_id && !signedIn && (84<Card>85<div style={{ fontSize: "75px", textAlign: "center" }}>86<Icon name="gift2" />87</div>88<InPlaceSignInOrUp89title="Redeem Voucher"90why="to redeem a voucher"91style={{ width: "450px" }}92onSuccess={() => {93router.push("/redeem");94setSignedIn(true);95}}96/>97</Card>98)}99100{(profile?.account_id || signedIn) && (101<Card style={{ background: "#fafafa" }}>102<Space direction="vertical" align="center">103<A href="/vouchers">104<Icon name="gift2" style={{ fontSize: "75px" }} />105</A>106<h1>Enter Voucher Code</h1>107<Input108disabled={state != "input"}109allowClear110autoFocus111size="large"112value={code}113onChange={(e) => {114setCode(e.target.value);115setError("");116}}117onPressEnter={redeemCode}118style={{ width: "300px", marginBottom: "15px" }}119/>120{error && (121<Alert122type="error"123message={"Error"}124description={error}125showIcon126style={{ width: "100%", marginBottom: "30px" }}127closable128onClose={() => setError("")}129/>130)}131{state != "redeemed" ? (132<Button133disabled={code.length < 8 || state != "input" || !!error}134size="large"135type="primary"136onClick={redeemCode}137>138{state == "input" && <>Redeem</>}139{state == "redeeming" && (140<Loading delay={0}>Redeeming...</Loading>141)}142</Button>143) : (144<Alert145showIcon146message={147"Success! You redeemed the voucher, which added the following to your account:"148}149type="success"150description={151<DisplayCreatedItems createdItems={createdItems} />152}153/>154)}155{state == "redeemed" && (156<div style={{ textAlign: "center", marginTop: "15px" }}>157<Button158onClick={() => {159setState("input");160setCode("");161setError("");162setCreatedItems(null);163}}164>165Redeem Another Voucher166</Button>167</div>168)}169<Divider orientation="left" style={{ width: "400px" }}>170<A href="https://doc.cocalc.com/vouchers.html">171<Icon name="medkit" /> Vouchers172</A>173</Divider>174<div175style={{176color: "#666",177maxWidth: "450px",178}}179>180<p>181When you redeem a voucher code,{" "}182<A href="/settings/purchases" external>183credit184</A>{" "}185will be added to your account186{profile?.email_address != null ? (187<A href="/config/account/email">{` ${profile?.email_address}`}</A>188) : (189""190)}191.192</p>193<p>194Once you redeem a voucher code, you can use the195corresponding{" "}196<A href="/settings/purchases" external>197credit198</A>{" "}199to make purchases.200</p>201<p>202You can browse{" "}203<A href="/vouchers/redeemed">204all vouchers you have already redeemed.205</A>{" "}206</p>207<p>208If you have any questions,{" "}209<A href="/support">contact support</A> and{" "}210<A href="https://doc.cocalc.com/vouchers.html">211read the documentation212</A>213.214</p>215216<div style={{ textAlign: "center" }}>217<A href="/vouchers">218<b>The Voucher Center</b>219</A>220</div>221</div>222</Space>223</Card>224)}225</div>226<Footer />227</Layout.Content>{" "}228</Layout>229</Customize>230);231}232233function DisplayCreatedItems({ createdItems }) {234if (createdItems == null) {235return null;236}237return (238<ol>239{createdItems.map((item, n) => (240<DisplayCreatedItem item={item} key={n} />241))}242</ol>243);244}245246function DisplayCreatedItem({ item }) {247if (item.type == "cash") {248return (249<li>250{currency(item.amount)} was credited{" "}251<A href={`/settings/purchases#id=${item.purchase_id}`} external>252to your account253</A>{" "}254(Id: {item.purchase_id})255</li>256);257} else {258return (259<li>260<pre>{JSON.stringify(item)}</pre>261</li>262);263}264}265266export async function getServerSideProps(context) {267return await withCustomize({ context });268}269270271