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 {