From b6cc1765db5f820bf1abfc65091aa3577d8df9ee Mon Sep 17 00:00:00 2001 From: Kieran Date: Mon, 17 Apr 2023 21:33:55 +0100 Subject: [PATCH] feat: implement renewal/complete new --- .../app/src/Nip05/SnortServiceProvider.ts | 9 ++ .../Pages/subscribe/ManageSubscription.tsx | 61 ++++++--- packages/app/src/Pages/subscribe/index.tsx | 126 +++++++++++------- packages/app/src/SnortApi.ts | 22 ++- 4 files changed, 151 insertions(+), 67 deletions(-) diff --git a/packages/app/src/Nip05/SnortServiceProvider.ts b/packages/app/src/Nip05/SnortServiceProvider.ts index 4fcc6d36..c4d58a1d 100644 --- a/packages/app/src/Nip05/SnortServiceProvider.ts +++ b/packages/app/src/Nip05/SnortServiceProvider.ts @@ -42,6 +42,15 @@ export default class SnortServiceProvider extends ServiceProvider { return this.getJsonAuthd(`/${id}`, "PATCH", obj); } + async registerForSubscription(handle: string, domain: string, id: string) { + return this.getJsonAuthd(`/registration/register/${id}`, "PUT", { + name: handle, + domain, + pk: "", + ref: "snort", + }); + } + async getJsonAuthd( path: string, method?: "GET" | string, diff --git a/packages/app/src/Pages/subscribe/ManageSubscription.tsx b/packages/app/src/Pages/subscribe/ManageSubscription.tsx index 6ed3a787..bfd37026 100644 --- a/packages/app/src/Pages/subscribe/ManageSubscription.tsx +++ b/packages/app/src/Pages/subscribe/ManageSubscription.tsx @@ -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>(); - const [error, setError] = useState(""); + const [error, setError] = useState(); + 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 ; } @@ -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 (
@@ -87,17 +101,26 @@ export default function ManageSubscriptionPage() {

)} - {daysToExpire < 0 && ( + {isExpired && (

)} + {isNew && ( +

+ +

+ )}
- {isExpired && ( + {(isExpired || isNew) && (
- + renew(a.id)}> + {isExpired ? ( + + ) : ( + + )} +
)}
@@ -117,7 +140,15 @@ export default function ManageSubscriptionPage() { />

)} - {error && {error}} + {error && {mapSubscriptionErrorCode(error)}} + setInvoice("")} + title={formatMessage({ + defaultMessage: "Renew subscription", + })} + /> ); } diff --git a/packages/app/src/Pages/subscribe/index.tsx b/packages/app/src/Pages/subscribe/index.tsx index ae3d1c74..78cae993 100644 --- a/packages/app/src/Pages/subscribe/index.tsx +++ b/packages/app/src/Pages/subscribe/index.tsx @@ -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,67 +38,91 @@ export function mapFeatureName(k: LockedFeatures) { } } +export function mapSubscriptionErrorCode(c: SubscriptionError) { + switch (c.code) { + case SubscriptionErrorCode.InternalError: + return ; + case SubscriptionErrorCode.SubscriptionActive: + return ; + case SubscriptionErrorCode.Duplicate: + return ; + default: + return c.message; + } +} + export function SubscribePage() { const publisher = useEventPublisher(); const api = new SnortApi(undefined, publisher); const [invoice, setInvoice] = useState(""); + const [error, setError] = useState(); async function subscribe(type: number) { - const rsp = await api.createSubscription(type); - setInvoice(rsp.pr); + setError(undefined); + try { + const rsp = await api.createSubscription(type); + setInvoice(rsp.pr); + } catch (e) { + if (e instanceof SubscriptionError) { + setError(e); + } + } } return ( -
- {Plans.map(a => { - const lower = Plans.filter(b => b.id < a.id); - return ( -
-
-

{mapPlanName(a.id)}

-

- {formatShort(a.price)} sats/mo, - }} - /> - : -

- - - -
    - {a.unlocks.map(b => ( -
  • {mapFeatureName(b)}
  • - ))} - {lower.map(b => ( -
  • - -
  • - ))} -
+ <> +
+ {Plans.map(a => { + const lower = Plans.filter(b => b.id < a.id); + return ( +
+
+

{mapPlanName(a.id)}

+

+ {formatShort(a.price)} sats/mo, + }} + /> + : +

+ + + +
    + {a.unlocks.map(b => ( +
  • {mapFeatureName(b)}
  • + ))} + {lower.map(b => ( +
  • + +
  • + ))} +
+
+
+ subscribe(a.id)}> + {a.disabled ? ( + + ) : ( + + )} + +
-
- subscribe(a.id)}> - {a.disabled ? ( - - ) : ( - - )} - -
-
- ); - })} + ); + })} +
+ {error && {mapSubscriptionErrorCode(error)}} setInvoice("")} /> -
+ ); } diff --git a/packages/app/src/SnortApi.ts b/packages/app/src/SnortApi.ts index 63917cce..f2acee76 100644 --- a/packages/app/src/SnortApi.ts +++ b/packages/app/src/SnortApi.ts @@ -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(`api/v1/subscription?type=${type}`, "PUT"); } + renewSubscription(id: string) { + return this.#getJsonAuthd(`api/v1/subscription/${id}/renew`, "GET"); + } + listSubscriptions() { return this.#getJsonAuthd>("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; }