Add pay button
This commit is contained in:
parent
baec1247fb
commit
4ad036ac9b
@ -15,6 +15,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
<script src="https://btcpay.v0l.io/modal/btcpay.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
78
src/App.tsx
78
src/App.tsx
@ -1,37 +1,59 @@
|
|||||||
|
import { CostInterval, DiskType, MachineSpec } from "./api"
|
||||||
|
import VpsCard from "./components/vps-card"
|
||||||
|
import { GiB } from "./const"
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const offers: Array<MachineSpec> = [
|
||||||
|
{
|
||||||
|
id: "2x2x80",
|
||||||
|
cpu: 2,
|
||||||
|
ram: 2 * GiB,
|
||||||
|
disk: {
|
||||||
|
type: DiskType.SSD,
|
||||||
|
size: 80 * GiB
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
interval: CostInterval.Month,
|
||||||
|
count: 3,
|
||||||
|
currency: "EUR",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "4x4x160",
|
||||||
|
cpu: 4,
|
||||||
|
ram: 4 * GiB,
|
||||||
|
disk: {
|
||||||
|
type: DiskType.SSD,
|
||||||
|
size: 160 * GiB
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
interval: CostInterval.Month,
|
||||||
|
count: 5,
|
||||||
|
currency: "EUR",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "8x8x400",
|
||||||
|
cpu: 8,
|
||||||
|
ram: 8 * GiB,
|
||||||
|
disk: {
|
||||||
|
type: DiskType.SSD,
|
||||||
|
size: 400 * GiB
|
||||||
|
},
|
||||||
|
cost: {
|
||||||
|
interval: CostInterval.Month,
|
||||||
|
count: 12,
|
||||||
|
currency: "EUR",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
return (
|
return (
|
||||||
<div className="w-[700px] mx-auto m-2 p-2">
|
<div className="w-[700px] mx-auto m-2 p-2">
|
||||||
<h1>lnvps.com</h1>
|
<h1>LNVPS</h1>
|
||||||
|
|
||||||
<h1>VPS</h1>
|
<h1>VPS</h1>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div className="grid grid-cols-3 gap-2">
|
||||||
<div className="rounded-xl border border-netrual-500 px-2 py-3">
|
{offers.map(a => <VpsCard spec={a} />)}
|
||||||
<h2>2x2x80</h2>
|
|
||||||
<ul>
|
|
||||||
<li>CPU: 2vCPU</li>
|
|
||||||
<li>RAM: 2GB</li>
|
|
||||||
<li>SSD: 80GB</li>
|
|
||||||
</ul>
|
|
||||||
<h2>3 EUR/mo</h2>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl border border-netrual-500 px-2 py-3">
|
|
||||||
<h2>4x4x160</h2>
|
|
||||||
<ul>
|
|
||||||
<li>CPU: 4vCPU</li>
|
|
||||||
<li>RAM: 4GB</li>
|
|
||||||
<li>SSD: 160GB</li>
|
|
||||||
</ul>
|
|
||||||
<h2>5 EUR/mo</h2>
|
|
||||||
</div>
|
|
||||||
<div className="rounded-xl border border-netrual-500 px-2 py-3">
|
|
||||||
<h2>8x8x400</h2>
|
|
||||||
<ul>
|
|
||||||
<li>CPU: 8vCPU</li>
|
|
||||||
<li>RAM: 8GB</li>
|
|
||||||
<li>SSD: 400GB</li>
|
|
||||||
</ul>
|
|
||||||
<h2>12 EUR/mo</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<small>
|
<small>
|
||||||
All VPS come with 1x IPv4 and 1x IPv6 address and unmetered traffic.
|
All VPS come with 1x IPv4 and 1x IPv6 address and unmetered traffic.
|
||||||
|
28
src/api.ts
Normal file
28
src/api.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export interface MachineSpec {
|
||||||
|
id: string;
|
||||||
|
cpu: number;
|
||||||
|
ram: number;
|
||||||
|
disk: {
|
||||||
|
type: DiskType;
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
cost: {
|
||||||
|
interval: CostInterval;
|
||||||
|
count: number;
|
||||||
|
currency: CostCurrency;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DiskType {
|
||||||
|
HDD,
|
||||||
|
SSD,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CostInterval {
|
||||||
|
Hour,
|
||||||
|
Day,
|
||||||
|
Month,
|
||||||
|
Year,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CostCurrency = "EUR" | "USD" | "BTC";
|
14
src/components/cost.tsx
Normal file
14
src/components/cost.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { CostInterval, MachineSpec } from "../api";
|
||||||
|
|
||||||
|
export default function CostLabel({ cost }: { cost: MachineSpec["cost"] }) {
|
||||||
|
function intervalName(n: number) {
|
||||||
|
switch (n) {
|
||||||
|
case CostInterval.Hour: return "Hour"
|
||||||
|
case CostInterval.Day: return "Day"
|
||||||
|
case CostInterval.Month: return "Month"
|
||||||
|
case CostInterval.Year: return "Year"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{cost.count} {cost.currency}/{intervalName(cost.interval)}</>
|
||||||
|
}
|
75
src/components/pay-button.css
Normal file
75
src/components/pay-button.css
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
.btcpay-form {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btcpay-form--inline {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.btcpay-form--block {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.btcpay-form--inline .submit {
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
.btcpay-form--block select {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.btcpay-form .btcpay-custom-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.btcpay-custom {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btcpay-form .plus-minus {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
background: #dfe0e1;
|
||||||
|
height: 30px;
|
||||||
|
width: 45px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 60px;
|
||||||
|
margin: auto 5px;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.btcpay-form select {
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
color: currentColor;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
display: block;
|
||||||
|
padding: 1px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.btcpay-form select:hover {
|
||||||
|
border-color: #ccc;
|
||||||
|
}
|
||||||
|
.btcpay-form option {
|
||||||
|
color: #000;
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.btcpay-input-price {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 25px;
|
||||||
|
margin: auto;
|
||||||
|
border-radius: 5px;
|
||||||
|
line-height: 35px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.btcpay-input-price::-webkit-outer-spin-button,
|
||||||
|
.btcpay-input-price::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
30
src/components/pay-button.tsx
Normal file
30
src/components/pay-button.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { MachineSpec } from "../api";
|
||||||
|
|
||||||
|
import "./pay-button.css"
|
||||||
|
|
||||||
|
export default function VpsPayButton({ spec }: { spec: MachineSpec }) {
|
||||||
|
const serverUrl = "https://btcpay.v0l.io/api/v1/invoices";
|
||||||
|
|
||||||
|
function handleFormSubmit(event: React.FormEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
const form = event.target as HTMLFormElement;
|
||||||
|
const xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function () {
|
||||||
|
if (this.readyState == 4 && this.status == 200 && this.responseText) {
|
||||||
|
window.btcpay.appendInvoiceFrame(JSON.parse(this.responseText).invoiceId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.open('POST', serverUrl, true);
|
||||||
|
xhttp.send(new FormData(form));
|
||||||
|
}
|
||||||
|
|
||||||
|
return <form method="POST" action={serverUrl} className="btcpay-form btcpay-form--block" onSubmit={handleFormSubmit}>
|
||||||
|
<input type="hidden" name="storeId" value="CdaHy1puLx4kLC9BG3A9mu88XNyLJukMJRuuhAfbDrxg" />
|
||||||
|
<input type="hidden" name="jsonResponse" value="true" />
|
||||||
|
<input type="hidden" name="orderId" value={spec.id} />
|
||||||
|
<input type="hidden" name="price" value={spec.cost.count} />
|
||||||
|
<input type="hidden" name="currency" value={spec.cost.currency} />
|
||||||
|
<input type="image" className="submit" name="submit" src="https://btcpay.v0l.io/img/paybutton/pay.svg"
|
||||||
|
alt="Pay with BTCPay Server, a Self-Hosted Bitcoin Payment Processor" />
|
||||||
|
</form>
|
||||||
|
}
|
16
src/components/vps-card.tsx
Normal file
16
src/components/vps-card.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { DiskType, MachineSpec } from "../api";
|
||||||
|
import CostLabel from "./cost";
|
||||||
|
import VpsPayButton from "./pay-button";
|
||||||
|
|
||||||
|
export default function VpsCard({ spec }: { spec: MachineSpec }) {
|
||||||
|
return <div className="rounded-xl border border-netrual-500 px-2 py-3">
|
||||||
|
<h2>{spec.id}</h2>
|
||||||
|
<ul>
|
||||||
|
<li>CPU: {spec.cpu}vCPU</li>
|
||||||
|
<li>RAM: {spec.ram / 1024 / 1024 / 1024}GB</li>
|
||||||
|
<li>{spec.disk.type === DiskType.SSD ? "SSD" : "HDD"}: {spec.disk.size / 1024 / 1024 / 1024}GB</li>
|
||||||
|
</ul>
|
||||||
|
<h2><CostLabel cost={spec.cost} /></h2>
|
||||||
|
<VpsPayButton spec={spec} />
|
||||||
|
</div>
|
||||||
|
}
|
4
src/const.ts
Normal file
4
src/const.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const KiB = 1024;
|
||||||
|
export const MiB = KiB * 1024;
|
||||||
|
export const GiB = MiB * 1024;
|
||||||
|
export const TiB = GiB * 1024;
|
Loading…
x
Reference in New Issue
Block a user