Skip to content

Commit

Permalink
Cred customer purchasing (#12868)
Browse files Browse the repository at this point in the history
* Create a shop to purchase credentials exams for customers
* Modify checkout as required to accommodate changes for credentials
---------

Co-authored-by: MariaPaula Trujillo <mtruj013@fiu.edu>
  • Loading branch information
abhigyanghosh30 and mtruj013 committed Jul 26, 2023
1 parent 43dca33 commit e973f05
Show file tree
Hide file tree
Showing 20 changed files with 491 additions and 34 deletions.
11 changes: 9 additions & 2 deletions static/js/src/advantage/credentials/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import { Integrations } from "@sentry/tracing";
import { ReactQueryDevtools } from "react-query/devtools";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import CredManage from "./components/CredManage";
import CredShop from "./components/CredShop";
import CredKeyShop from "./components/CredKeyShop";
import CredPurchaseConfirmation from "./components/CredPurchaseConfirmation/CredPurchaseConfirmation";
import CredExamShop from "./components/CredExamShop/CredExamShop";
import CredWebhookResponses from "./components/CredWebhookResponses";

const oneHour = 1000 * 60 * 60;
Expand Down Expand Up @@ -38,8 +40,13 @@ function App() {
<QueryClientProvider client={queryClient}>
<Router basename="/credentials/shop">
<Routes>
<Route path="/" element={<CredShop />} />
<Route path="/" element={<CredExamShop />} />
<Route path="/keys" element={<CredKeyShop />} />
<Route path="/manage" element={<CredManage />} />
<Route
path="/order-thank-you"
element={<CredPurchaseConfirmation />}
/>
<Route
path="/webhook_responses"
element={<CredWebhookResponses />}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import React, { useState } from "react";
import {
Button,
Col,
RadioInput,
Row,
Strip,
} from "@canonical/react-components";
import classNames from "classnames";
import { currencyFormatter } from "advantage/react/utils";

const CredExamShop = () => {
const ExamProducts = [
{
id: "cue-linux-essentials",
longId: "lAK5jL8zvMjZOwaysIMQyGRAdOLgTQQH0xpezu2oYp74",
name: "CUE Linux Essentials",
period: "none",
price: {
currency: "USD",
value: 4900,
},
productID: "cue-linux-essentials",
status: "active",
private: false,
marketplace: "canonical-cube",
metadata: [
{
key: "description",
value:
"Prove your basic operational knowledge of Linux by demonstrating your ability to secure, operate and maintain basic system resources. Topics include user and group management, file and filesystem navigation, and logs and installation tasks related to system maintenance.",
},
],
},
{
id: "cue-02-desktop",
longId: "lAMGrt4buzUR0-faJqg-Ot6dgNLn7ubIpWiyDgOrsDCg",
name: "CUE.02 Desktop QuickCert",
price: { value: 4900, currency: "USD" },
productID: "cue-02-desktop",
canBeTrialled: false,
private: true,
marketplace: "canonical-cube",
metadata: [
{
key: "description",
value:
"Demonstrate your knowledge of Ubuntu Desktop administrative essentials. Topics include package management, system installation, data gathering, and managing printing and displays.",
},
],
},
{
id: "cue-03-server",
longId: "lAMGrt4buzUR0-faJqg-Ot6dgNLn7ubIpWiyDgOrsDCg",
name: "CUE.03 Server QuickCert",
price: { value: 4900, currency: "USD" },
productID: "cue-03-server",
canBeTrialled: false,
private: true,
marketplace: "canonical-cube",
metadata: [
{
key: "description",
value:
"Illustrate your knowledge of common Ubuntu Server administrative tasks and troubleshooting. Topics include job control, performance tuning, services management, and Bash scripting.",
},
],
},
];
const [exam, setExam] = useState(0);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setExam(parseInt(event.target.value));
localStorage.setItem("exam-selector", JSON.stringify(event?.target.value));
};
const handleSubmit = (
event:
| React.FormEvent<HTMLFormElement>
| React.MouseEvent<HTMLButtonElement>
) => {
event.preventDefault();
localStorage.setItem(
"shop-checkout-data",
JSON.stringify({
product: ExamProducts[exam],
quantity: 1,
action: "purchase",
})
);
location.href = "/account/checkout";
};
return (
<>
<Strip className="product-selector">
<Row>
<h2>Select an exam to purchase</h2>
</Row>
<Row className="u-hide--small">
{ExamProducts.map((examElement, examIndex) => {
return (
<Col size={4} key={examIndex}>
<div
className={classNames({
"p-card--radio--column": true,
"is-selected": exam == examIndex,
})}
>
<label className="p-radio">
<input
className="p-radio__input"
autoComplete="off"
type="radio"
aria-labelledby={examElement.id + "-label"}
value={examIndex}
checked={exam == examIndex}
onChange={handleChange}
disabled={examElement.private}
/>
<span className="p-radio__label">
<RadioInput
labelClassName="inner-label"
label={examElement.name}
checked={exam == examIndex}
value={examIndex}
onChange={handleChange}
disabled={examElement.private}
/>
<hr />
<p style={{ paddingLeft: "1rem", paddingRight: "1rem" }}>
{examElement.metadata[0].value}
</p>
<hr />
<h5
className="u-align--right"
style={{ paddingRight: "1rem" }}
>
{examElement.private
? "Coming Soon!"
: "Price: " +
currencyFormatter.format(
examElement.price.value / 100
)}
</h5>
</span>
</label>
</div>
</Col>
);
})}
</Row>
{ExamProducts.map((examElement, examIndex) => {
return (
<Col className="u-hide u-show--small" size={12} key={examIndex}>
<div
className={classNames({
"p-card--radio--column": true,
"is-selected": exam == examIndex,
})}
onChange={handleChange}
onClick={() => {
setExam(examIndex);
}}
>
<RadioInput
inline
label={examElement.name}
checked={exam == examIndex}
value={examIndex}
onChange={handleChange}
disabled={examElement.private}
/>
<span>
<p style={{ paddingLeft: "1rem", paddingRight: "1rem" }}>
{examElement.metadata[0].value}
</p>
</span>
<span>
<h5
className="u-align--right"
style={{ paddingRight: "1rem" }}
>
{examElement.private
? "Coming Soon!"
: "Price: " +
currencyFormatter.format(examElement.price.value / 100)}
</h5>
</span>
</div>
</Col>
);
})}
</Strip>
<section className="p-strip--light is-shallow p-shop-cart p-shop-cart--cue">
<Row className="u-sv3">
<Col size={6} className="p-text--small-caps">
Your Order
</Col>
</Row>
<Row>
<Col size={6} style={{ display: "flex" }}>
<p className="p-heading--2" style={{ marginBlock: "auto" }}>
{ExamProducts[exam].name}
</p>
</Col>
<Col size={3} small={2} style={{ display: "flex" }}>
<p className="p-heading--2" style={{ marginBlock: "auto" }}>
{currencyFormatter.format(
(ExamProducts[exam]?.price.value ?? 0) / 100 ?? 0
)}
</p>
</Col>
<Col
size={3}
small={2}
style={{ display: "flex" }}
className="u-align--right"
>
<Button
appearance="positive"
onClick={handleSubmit}
style={{ marginBlock: "auto" }}
>
Buy Now
</Button>
</Col>
</Row>
</section>
</>
);
};
export default CredExamShop;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./CredExamShop";
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useState } from "react";
import {
Button,
Col,
Form,
Input,
Row,
Strip,
} from "@canonical/react-components";
import { currencyFormatter } from "advantage/react/utils";

const CredKeyShop = () => {
const CUEExamKey = {
id: "cue-activation-key",
longId: "lAMGrt4buzUR0-faJqg-Ot6dgNLn7ubIpWiyDgOrsDCg",
name: "CUE Activation Key",
price: { value: 4900, currency: "USD" },
productID: "cue-activation-key",
canBeTrialled: false,
private: false,
marketplace: "canonical-cube",
};
const checkoutData = localStorage.getItem("shop-checkout-data") || "{}";
const parsedCheckoutData = JSON.parse(checkoutData);
const initQuantity: number = parsedCheckoutData?.quantity;
const [quantity, setQuantity] = useState(initQuantity ?? 1);
const handleChange: React.ChangeEventHandler = (
event: React.ChangeEvent<HTMLInputElement>
) => {
event.preventDefault();
setQuantity(parseInt(event.target.value));
};
const handleSubmit = (
event:
| React.FormEvent<HTMLFormElement>
| React.MouseEvent<HTMLButtonElement>
) => {
event.preventDefault();
localStorage.setItem(
"shop-checkout-data",
JSON.stringify({
product: CUEExamKey,
quantity: quantity,
action: "purchase",
})
);
location.href = "/account/checkout";
};
return (
<>
<Strip>
<Row>
<h3>How many exams attempts do you need?</h3>
</Row>
<Row>
<Col size={6}>
<Form onSubmit={handleSubmit}>
<Input
type="number"
id="keyQuantity"
min="1"
value={quantity}
onChange={handleChange}
/>
</Form>
</Col>
</Row>
<Row>
<p>
Each exam attempt allows you to register for one or more of the
following certifications:
<ul>
<li>CUE.01: Linux</li>
<li>CUE.02: Desktop</li>
<li>CUE.03: Server</li>
</ul>
</p>
</Row>
</Strip>
<section className="p-strip--light is-shallow p-shop-cart">
<Row className="u-sv3">
<Col size={6} className="p-heading--2">
Your Order
</Col>
</Row>
<Row className="u-sv3">
<Col size={6}>{quantity} x Exam attempt key</Col>
<Col size={3}>
{currencyFormatter.format(
((CUEExamKey?.price.value ?? 0) / 100) * (Number(quantity) ?? 0)
)}
</Col>
<Col size={3}>
<Button appearance="positive" type="submit" onClick={handleSubmit}>
Buy Now
</Button>
</Col>
</Row>
</section>
</>
);
};
export default CredKeyShop;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./CredKeyShop";
Loading

0 comments on commit e973f05

Please sign in to comment.