diff --git a/packages/app/src/Element/Nip5Service.tsx b/packages/app/src/Element/Nip5Service.tsx
index 4106d944..20962783 100644
--- a/packages/app/src/Element/Nip5Service.tsx
+++ b/packages/app/src/Element/Nip5Service.tsx
@@ -21,6 +21,7 @@ import { useUserProfile } from "Hooks/useUserProfile";
import useEventPublisher from "Feed/EventPublisher";
import { debounce } from "Util";
import useLogin from "Hooks/useLogin";
+import SnortServiceProvider from "Nip05/SnortServiceProvider";
import messages from "./messages";
@@ -31,6 +32,7 @@ type Nip05ServiceProps = {
link: string;
supportLink: string;
helpText?: boolean;
+ forSubscription?: string;
onChange?(h: string): void;
onSuccess?(h: string): void;
};
@@ -188,6 +190,22 @@ export default function Nip5Service(props: Nip05ServiceProps) {
}
}
+ async function claimForSubscription(handle: string, domain: string, sub: string) {
+ if (!pubkey || !publisher) {
+ return;
+ }
+
+ const svcEx = new SnortServiceProvider(publisher, props.service);
+ const rsp = await svcEx.registerForSubscription(handle, domain, sub);
+ if ("error" in rsp) {
+ setError(rsp);
+ } else {
+ if (props.onSuccess) {
+ const nip05 = `${handle}@${domain}`;
+ props.onSuccess(nip05);
+ }
+ }
+ }
async function updateProfile(handle: string, domain: string) {
if (user && publisher) {
const nip05 = `${handle}@${domain}`;
@@ -245,16 +263,27 @@ export default function Nip5Service(props: Nip05ServiceProps) {
)}
{availabilityResponse?.available && !registerStatus && (
-
-
-
- {availabilityResponse.quote?.data.type}
-
-
startBuy(handle, domain)}>
-
+ {!props.forSubscription && (
+
+
+
+ {availabilityResponse.quote?.data.type}
+
+ )}
+
+ props.forSubscription
+ ? claimForSubscription(handle, domain, props.forSubscription)
+ : startBuy(handle, domain)
+ }>
+ {props.forSubscription ? (
+
+ ) : (
+
+ )}
)}
diff --git a/packages/app/src/Pages/Verification.tsx b/packages/app/src/Pages/Verification.tsx
index 25ebfd5b..158a0889 100644
--- a/packages/app/src/Pages/Verification.tsx
+++ b/packages/app/src/Pages/Verification.tsx
@@ -7,14 +7,16 @@ import messages from "./messages";
import "./Verification.css";
-export const services = [
- {
- name: "Snort",
- service: `${ApiHost}/api/v1/n5sp`,
- link: "https://snort.social/",
- supportLink: "https://snort.social/help",
- about: ,
- },
+export const SnortNostrAddressService = {
+ name: "Snort",
+ service: `${ApiHost}/api/v1/n5sp`,
+ link: "https://snort.social/",
+ supportLink: "https://snort.social/help",
+ about: ,
+};
+
+export const Nip5Services = [
+ SnortNostrAddressService,
{
name: "Nostr Plebs",
service: "https://nostrplebs.com/api/v1",
@@ -48,7 +50,7 @@ export default function VerificationPage() {
- {services.map(a => (
+ {Nip5Services.map(a => (
))}
diff --git a/packages/app/src/Pages/new/GetVerified.tsx b/packages/app/src/Pages/new/GetVerified.tsx
index d3d7436f..a420596c 100644
--- a/packages/app/src/Pages/new/GetVerified.tsx
+++ b/packages/app/src/Pages/new/GetVerified.tsx
@@ -3,7 +3,7 @@ import { FormattedMessage } from "react-intl";
import { useNavigate } from "react-router-dom";
import Logo from "Element/Logo";
-import { services } from "Pages/Verification";
+import { Nip5Services } from "Pages/Verification";
import Nip5Service from "Element/Nip5Service";
import ProfileImage from "Element/ProfileImage";
import { useUserProfile } from "Hooks/useUserProfile";
@@ -66,7 +66,7 @@ export default function GetVerified() {
setIsVerified(true)}
@@ -85,7 +85,7 @@ export default function GetVerified() {
setIsVerified(true)}
diff --git a/packages/app/src/Pages/subscribe/ManageSubscription.tsx b/packages/app/src/Pages/subscribe/ManageSubscription.tsx
index bb5b5aea..5da37bd4 100644
--- a/packages/app/src/Pages/subscribe/ManageSubscription.tsx
+++ b/packages/app/src/Pages/subscribe/ManageSubscription.tsx
@@ -1,23 +1,19 @@
import { useEffect, useState } from "react";
-import { FormattedDate, FormattedMessage, FormattedNumber, useIntl } from "react-intl";
+import { FormattedMessage } from "react-intl";
import { Link } from "react-router-dom";
import PageSpinner from "Element/PageSpinner";
import useEventPublisher from "Feed/EventPublisher";
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";
+import { mapSubscriptionErrorCode } from ".";
+import SubscriptionCard from "./SubscriptionCard";
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 [invoice, setInvoice] = useState("");
useEffect(() => {
(async () => {
@@ -32,17 +28,6 @@ export default function ManageSubscriptionPage() {
})();
}, []);
- 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 ;
}
@@ -51,81 +36,9 @@ export default function ManageSubscriptionPage() {
- {subs.map(a => {
- const created = new Date(a.created);
- const expires = new Date(a.expires);
- 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 = a.state === "expired";
- const isNew = a.state === "new";
- return (
-
-
-
- {mapPlanName(a.type)}
-
-
-
-
- :
-
-
- {daysToExpire >= 1 && (
-
-
- :
-
-
- )}
- {daysToExpire >= 0 && daysToExpire < 1 && (
-
-
- :
-
-
- )}
- {isExpired && (
-
-
-
- )}
- {isNew && (
-
-
-
- )}
-
- {(isExpired || isNew) && (
-
-
renew(a.id)}>
- {isExpired ? (
-
- ) : (
-
- )}
-
-
- )}
-
- );
- })}
+ {subs.map(a => (
+
+ ))}
{subs.length === 0 && (
)}
{error && {mapSubscriptionErrorCode(error)}}
- setInvoice("")}
- title={formatMessage({
- defaultMessage: "Pay for subscription",
- })}
- />
>
);
}
diff --git a/packages/app/src/Pages/subscribe/SubscriptionCard.tsx b/packages/app/src/Pages/subscribe/SubscriptionCard.tsx
new file mode 100644
index 00000000..6bc053be
--- /dev/null
+++ b/packages/app/src/Pages/subscribe/SubscriptionCard.tsx
@@ -0,0 +1,137 @@
+import { FormattedMessage, FormattedDate, FormattedNumber, useIntl } from "react-intl";
+import { useState } from "react";
+
+import SnortApi, { Subscription, SubscriptionError } from "SnortApi";
+import { mapPlanName, mapSubscriptionErrorCode } from ".";
+import AsyncButton from "Element/AsyncButton";
+import Icon from "Icons/Icon";
+import useEventPublisher from "Feed/EventPublisher";
+import SendSats from "Element/SendSats";
+import Nip5Service from "Element/Nip5Service";
+import { SnortNostrAddressService } from "Pages/Verification";
+import Nip05 from "Element/Nip05";
+
+export default function SubscriptionCard({ sub }: { sub: Subscription }) {
+ const publisher = useEventPublisher();
+ const { formatMessage } = useIntl();
+
+ const created = new Date(sub.created * 1000);
+ const expires = new Date(sub.expires * 1000);
+ 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 = sub.state === "expired";
+ const isNew = sub.state === "new";
+ const isPaid = sub.state === "paid";
+
+ const [invoice, setInvoice] = useState("");
+ const [error, setError] = useState();
+
+ async function renew(id: string) {
+ const api = new SnortApi(undefined, publisher);
+ try {
+ const rsp = await api.renewSubscription(id);
+ setInvoice(rsp.pr);
+ } catch (e) {
+ if (e instanceof SubscriptionError) {
+ setError(e);
+ }
+ }
+ }
+
+ function subFeatures() {
+ return (
+ <>
+ {!sub.handle && (
+ <>
+
+
+
+ (sub.handle = h)}
+ />
+ >
+ )}
+ {sub.handle && }
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+ {mapPlanName(sub.type)}
+
+
+
+
+ :
+
+
+ {daysToExpire >= 1 && (
+
+
+ :
+
+
+ )}
+ {daysToExpire >= 0 && daysToExpire < 1 && (
+
+
+ :
+
+
+ )}
+ {isExpired && (
+
+
+
+ )}
+ {isNew && (
+
+
+
+ )}
+
+ {(isExpired || isNew) && (
+
+
renew(sub.id)}>
+ {isExpired ? : }
+
+
+ )}
+ {isPaid && subFeatures()}
+
+ setInvoice("")}
+ title={formatMessage({
+ defaultMessage: "Pay for subscription",
+ })}
+ />
+ {error && {mapSubscriptionErrorCode(error)}}
+ >
+ );
+}
diff --git a/packages/app/src/SnortApi.ts b/packages/app/src/SnortApi.ts
index f2acee76..f7625796 100644
--- a/packages/app/src/SnortApi.ts
+++ b/packages/app/src/SnortApi.ts
@@ -20,9 +20,10 @@ export interface InvoiceResponse {
export interface Subscription {
id: string;
type: SubscriptionType;
- created: string;
- expires: string;
+ created: number;
+ expires: number;
state: "new" | "expired" | "paid";
+ handle?: string;
}
export enum SubscriptionErrorCode {