feat: implement renewal/complete new
This commit is contained in:
parent
c59dda1e49
commit
b6cc1765db
@ -42,6 +42,15 @@ export default class SnortServiceProvider extends ServiceProvider {
|
||||
return this.getJsonAuthd<object>(`/${id}`, "PATCH", obj);
|
||||
}
|
||||
|
||||
async registerForSubscription(handle: string, domain: string, id: string) {
|
||||
return this.getJsonAuthd<object>(`/registration/register/${id}`, "PUT", {
|
||||
name: handle,
|
||||
domain,
|
||||
pk: "",
|
||||
ref: "snort",
|
||||
});
|
||||
}
|
||||
|
||||
async getJsonAuthd<T>(
|
||||
path: string,
|
||||
method?: "GET" | string,
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedDate, FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { FormattedDate, FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import PageSpinner from "Element/PageSpinner";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import SnortApi, { Subscription } from "SnortApi";
|
||||
import { mapPlanName } from ".";
|
||||
import SnortApi, { Subscription, SubscriptionError } from "SnortApi";
|
||||
import { mapPlanName, mapSubscriptionErrorCode } from ".";
|
||||
import Icon from "Icons/Icon";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import SendSats from "Element/SendSats";
|
||||
|
||||
export default function ManageSubscriptionPage() {
|
||||
const publisher = useEventPublisher();
|
||||
const { formatMessage } = useIntl();
|
||||
const api = new SnortApi(undefined, publisher);
|
||||
|
||||
const [subs, setSubs] = useState<Array<Subscription>>();
|
||||
const [error, setError] = useState("");
|
||||
const [error, setError] = useState<SubscriptionError>();
|
||||
const [invoice, setInvoice] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@ -21,15 +25,24 @@ export default function ManageSubscriptionPage() {
|
||||
const s = await api.listSubscriptions();
|
||||
setSubs(s);
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
setError(e.message);
|
||||
} else {
|
||||
setError("Unknown error");
|
||||
if (e instanceof SubscriptionError) {
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
async function renew(id: string) {
|
||||
try {
|
||||
const rsp = await api.renewSubscription(id);
|
||||
setInvoice(rsp.pr);
|
||||
} catch (e) {
|
||||
if (e instanceof SubscriptionError) {
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subs === undefined) {
|
||||
return <PageSpinner />;
|
||||
}
|
||||
@ -44,7 +57,8 @@ export default function ManageSubscriptionPage() {
|
||||
const now = new Date();
|
||||
const daysToExpire = Math.floor((expires.getTime() - now.getTime()) / 8.64e7);
|
||||
const hoursToExpire = Math.floor((expires.getTime() - now.getTime()) / 3.6e6);
|
||||
const isExpired = expires < now;
|
||||
const isExpired = a.state === "expired";
|
||||
const isNew = a.state === "new";
|
||||
return (
|
||||
<div key={a.id} className="card">
|
||||
<div className="flex card-title">
|
||||
@ -87,17 +101,26 @@ export default function ManageSubscriptionPage() {
|
||||
</time>
|
||||
</p>
|
||||
)}
|
||||
{daysToExpire < 0 && (
|
||||
{isExpired && (
|
||||
<p className="f-1 error">
|
||||
<FormattedMessage defaultMessage="Expired" />
|
||||
</p>
|
||||
)}
|
||||
{isNew && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Unpaid" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{isExpired && (
|
||||
{(isExpired || isNew) && (
|
||||
<div className="flex">
|
||||
<button>
|
||||
<AsyncButton onClick={() => renew(a.id)}>
|
||||
{isExpired ? (
|
||||
<FormattedMessage defaultMessage="Renew" />
|
||||
</button>
|
||||
) : (
|
||||
<FormattedMessage defaultMessage="Pay Now" />
|
||||
)}
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -117,7 +140,15 @@ export default function ManageSubscriptionPage() {
|
||||
/>
|
||||
</p>
|
||||
)}
|
||||
{error && <b className="error">{error}</b>}
|
||||
{error && <b className="error">{mapSubscriptionErrorCode(error)}</b>}
|
||||
<SendSats
|
||||
invoice={invoice}
|
||||
show={invoice !== ""}
|
||||
onClose={() => setInvoice("")}
|
||||
title={formatMessage({
|
||||
defaultMessage: "Renew subscription",
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import { LockedFeatures, Plans, SubscriptionType } from "Subscription";
|
||||
import ManageSubscriptionPage from "Pages/subscribe/ManageSubscription";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import SnortApi from "SnortApi";
|
||||
import SnortApi, { SubscriptionError, SubscriptionErrorCode } from "SnortApi";
|
||||
import SendSats from "Element/SendSats";
|
||||
|
||||
export function mapPlanName(id: number) {
|
||||
@ -38,17 +38,39 @@ export function mapFeatureName(k: LockedFeatures) {
|
||||
}
|
||||
}
|
||||
|
||||
export function mapSubscriptionErrorCode(c: SubscriptionError) {
|
||||
switch (c.code) {
|
||||
case SubscriptionErrorCode.InternalError:
|
||||
return <FormattedMessage defaultMessage="Internal error: {msg}" values={{ msg: c.message }} />;
|
||||
case SubscriptionErrorCode.SubscriptionActive:
|
||||
return <FormattedMessage defaultMessage="You subscription is still active, you can't renew yet" />;
|
||||
case SubscriptionErrorCode.Duplicate:
|
||||
return <FormattedMessage defaultMessage="You already have a subscription of this type, please renew or pay" />;
|
||||
default:
|
||||
return c.message;
|
||||
}
|
||||
}
|
||||
|
||||
export function SubscribePage() {
|
||||
const publisher = useEventPublisher();
|
||||
const api = new SnortApi(undefined, publisher);
|
||||
const [invoice, setInvoice] = useState("");
|
||||
const [error, setError] = useState<SubscriptionError>();
|
||||
|
||||
async function subscribe(type: number) {
|
||||
setError(undefined);
|
||||
try {
|
||||
const rsp = await api.createSubscription(type);
|
||||
setInvoice(rsp.pr);
|
||||
} catch (e) {
|
||||
if (e instanceof SubscriptionError) {
|
||||
setError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex subscribe-page">
|
||||
{Plans.map(a => {
|
||||
const lower = Plans.filter(b => b.id < a.id);
|
||||
@ -97,8 +119,10 @@ export function SubscribePage() {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<SendSats invoice={invoice} show={invoice !== ""} onClose={() => setInvoice("")} />
|
||||
</div>
|
||||
{error && <b className="error">{mapSubscriptionErrorCode(error)}</b>}
|
||||
<SendSats invoice={invoice} show={invoice !== ""} onClose={() => setInvoice("")} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,22 @@ export interface Subscription {
|
||||
type: SubscriptionType;
|
||||
created: string;
|
||||
expires: string;
|
||||
state: "new" | "expired" | "paid";
|
||||
}
|
||||
|
||||
export enum SubscriptionErrorCode {
|
||||
InternalError = 1,
|
||||
SubscriptionActive = 2,
|
||||
Duplicate = 3,
|
||||
}
|
||||
|
||||
export class SubscriptionError extends Error {
|
||||
code: SubscriptionErrorCode;
|
||||
|
||||
constructor(msg: string, code: SubscriptionErrorCode) {
|
||||
super(msg);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
export default class SnortApi {
|
||||
@ -49,6 +65,10 @@ export default class SnortApi {
|
||||
return this.#getJsonAuthd<InvoiceResponse>(`api/v1/subscription?type=${type}`, "PUT");
|
||||
}
|
||||
|
||||
renewSubscription(id: string) {
|
||||
return this.#getJsonAuthd<InvoiceResponse>(`api/v1/subscription/${id}/renew`, "GET");
|
||||
}
|
||||
|
||||
listSubscriptions() {
|
||||
return this.#getJsonAuthd<Array<Subscription>>("api/v1/subscription");
|
||||
}
|
||||
@ -93,7 +113,7 @@ export default class SnortApi {
|
||||
|
||||
const obj = await rsp.json();
|
||||
if ("error" in obj) {
|
||||
throw new Error(obj.error);
|
||||
throw new SubscriptionError(obj.error, obj.code);
|
||||
}
|
||||
return obj as T;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user