claim handle
This commit is contained in:
parent
766c1fe472
commit
6354fc3a17
@ -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 && (
|
||||
<div className="flex">
|
||||
<div className="mr10">
|
||||
<FormattedMessage
|
||||
{...messages.Sats}
|
||||
values={{ n: formatShort(unwrap(availabilityResponse.quote?.price)) }}
|
||||
/>
|
||||
<br />
|
||||
<small>{availabilityResponse.quote?.data.type}</small>
|
||||
</div>
|
||||
<AsyncButton onClick={() => startBuy(handle, domain)}>
|
||||
<FormattedMessage {...messages.BuyNow} />
|
||||
{!props.forSubscription && (
|
||||
<div className="mr10">
|
||||
<FormattedMessage
|
||||
{...messages.Sats}
|
||||
values={{ n: formatShort(unwrap(availabilityResponse.quote?.price)) }}
|
||||
/>
|
||||
<br />
|
||||
<small>{availabilityResponse.quote?.data.type}</small>
|
||||
</div>
|
||||
)}
|
||||
<AsyncButton
|
||||
onClick={() =>
|
||||
props.forSubscription
|
||||
? claimForSubscription(handle, domain, props.forSubscription)
|
||||
: startBuy(handle, domain)
|
||||
}>
|
||||
{props.forSubscription ? (
|
||||
<FormattedMessage defaultMessage="Claim Now" />
|
||||
) : (
|
||||
<FormattedMessage {...messages.BuyNow} />
|
||||
)}
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
|
@ -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: <FormattedMessage {...messages.SnortSocialNip} />,
|
||||
},
|
||||
export const SnortNostrAddressService = {
|
||||
name: "Snort",
|
||||
service: `${ApiHost}/api/v1/n5sp`,
|
||||
link: "https://snort.social/",
|
||||
supportLink: "https://snort.social/help",
|
||||
about: <FormattedMessage {...messages.SnortSocialNip} />,
|
||||
};
|
||||
|
||||
export const Nip5Services = [
|
||||
SnortNostrAddressService,
|
||||
{
|
||||
name: "Nostr Plebs",
|
||||
service: "https://nostrplebs.com/api/v1",
|
||||
@ -48,7 +50,7 @@ export default function VerificationPage() {
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{services.map(a => (
|
||||
{Nip5Services.map(a => (
|
||||
<Nip5Service key={a.name} {...a} />
|
||||
))}
|
||||
</div>
|
||||
|
@ -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() {
|
||||
<div className="nip-container">
|
||||
<Nip5Service
|
||||
key="snort"
|
||||
{...services[0]}
|
||||
{...Nip5Services[0]}
|
||||
helpText={false}
|
||||
onChange={setNip05}
|
||||
onSuccess={() => setIsVerified(true)}
|
||||
@ -85,7 +85,7 @@ export default function GetVerified() {
|
||||
<div className="nip-container">
|
||||
<Nip5Service
|
||||
key="nostrplebs"
|
||||
{...services[1]}
|
||||
{...Nip5Services[1]}
|
||||
helpText={false}
|
||||
onChange={setNip05}
|
||||
onSuccess={() => setIsVerified(true)}
|
||||
|
@ -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<Array<Subscription>>();
|
||||
const [error, setError] = useState<SubscriptionError>();
|
||||
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 <PageSpinner />;
|
||||
}
|
||||
@ -51,81 +36,9 @@ export default function ManageSubscriptionPage() {
|
||||
<h2>
|
||||
<FormattedMessage defaultMessage="Subscriptions" />
|
||||
</h2>
|
||||
{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 (
|
||||
<div key={a.id} className="card">
|
||||
<div className="flex card-title">
|
||||
<Icon name="badge" className="mr5" size={25} />
|
||||
{mapPlanName(a.type)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Created" />
|
||||
:
|
||||
<time dateTime={created.toISOString()}>
|
||||
<FormattedDate value={created} dateStyle="full" />
|
||||
</time>
|
||||
</p>
|
||||
{daysToExpire >= 1 && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Expires" />
|
||||
:
|
||||
<time dateTime={expires.toISOString()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} days"
|
||||
values={{
|
||||
n: <FormattedNumber value={daysToExpire} maximumFractionDigits={0} />,
|
||||
}}
|
||||
/>
|
||||
</time>
|
||||
</p>
|
||||
)}
|
||||
{daysToExpire >= 0 && daysToExpire < 1 && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Expires" />
|
||||
:
|
||||
<time dateTime={expires.toISOString()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} hours"
|
||||
values={{
|
||||
n: <FormattedNumber value={hoursToExpire} maximumFractionDigits={0} />,
|
||||
}}
|
||||
/>
|
||||
</time>
|
||||
</p>
|
||||
)}
|
||||
{isExpired && (
|
||||
<p className="f-1 error">
|
||||
<FormattedMessage defaultMessage="Expired" />
|
||||
</p>
|
||||
)}
|
||||
{isNew && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Unpaid" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{(isExpired || isNew) && (
|
||||
<div className="flex">
|
||||
<AsyncButton onClick={() => renew(a.id)}>
|
||||
{isExpired ? (
|
||||
<FormattedMessage defaultMessage="Renew" />
|
||||
) : (
|
||||
<FormattedMessage defaultMessage="Pay Now" />
|
||||
)}
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{subs.map(a => (
|
||||
<SubscriptionCard sub={a} key={a.id} />
|
||||
))}
|
||||
{subs.length === 0 && (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
@ -141,14 +54,6 @@ export default function ManageSubscriptionPage() {
|
||||
</p>
|
||||
)}
|
||||
{error && <b className="error">{mapSubscriptionErrorCode(error)}</b>}
|
||||
<SendSats
|
||||
invoice={invoice}
|
||||
show={invoice !== ""}
|
||||
onClose={() => setInvoice("")}
|
||||
title={formatMessage({
|
||||
defaultMessage: "Pay for subscription",
|
||||
})}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
137
packages/app/src/Pages/subscribe/SubscriptionCard.tsx
Normal file
137
packages/app/src/Pages/subscribe/SubscriptionCard.tsx
Normal file
@ -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<SubscriptionError>();
|
||||
|
||||
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 && (
|
||||
<>
|
||||
<h3>
|
||||
<FormattedMessage defaultMessage="Claim your included Snort nostr address" />
|
||||
</h3>
|
||||
<Nip5Service
|
||||
{...SnortNostrAddressService}
|
||||
helpText={false}
|
||||
forSubscription={sub.id}
|
||||
onSuccess={h => (sub.handle = h)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{sub.handle && <Nip05 nip05={sub.handle} pubkey={""} verifyNip={false} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<div className="flex card-title">
|
||||
<Icon name="badge" className="mr5" size={25} />
|
||||
{mapPlanName(sub.type)}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Created" />
|
||||
:
|
||||
<time dateTime={created.toISOString()}>
|
||||
<FormattedDate value={created} dateStyle="full" />
|
||||
</time>
|
||||
</p>
|
||||
{daysToExpire >= 1 && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Expires" />
|
||||
:
|
||||
<time dateTime={expires.toISOString()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} days"
|
||||
values={{
|
||||
n: <FormattedNumber value={daysToExpire} maximumFractionDigits={0} />,
|
||||
}}
|
||||
/>
|
||||
</time>
|
||||
</p>
|
||||
)}
|
||||
{daysToExpire >= 0 && daysToExpire < 1 && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Expires" />
|
||||
:
|
||||
<time dateTime={expires.toISOString()}>
|
||||
<FormattedMessage
|
||||
defaultMessage="{n} hours"
|
||||
values={{
|
||||
n: <FormattedNumber value={hoursToExpire} maximumFractionDigits={0} />,
|
||||
}}
|
||||
/>
|
||||
</time>
|
||||
</p>
|
||||
)}
|
||||
{isExpired && (
|
||||
<p className="f-1 error">
|
||||
<FormattedMessage defaultMessage="Expired" />
|
||||
</p>
|
||||
)}
|
||||
{isNew && (
|
||||
<p className="f-1">
|
||||
<FormattedMessage defaultMessage="Unpaid" />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{(isExpired || isNew) && (
|
||||
<div className="flex">
|
||||
<AsyncButton onClick={() => renew(sub.id)}>
|
||||
{isExpired ? <FormattedMessage defaultMessage="Renew" /> : <FormattedMessage defaultMessage="Pay Now" />}
|
||||
</AsyncButton>
|
||||
</div>
|
||||
)}
|
||||
{isPaid && subFeatures()}
|
||||
</div>
|
||||
<SendSats
|
||||
invoice={invoice}
|
||||
show={invoice !== ""}
|
||||
onClose={() => setInvoice("")}
|
||||
title={formatMessage({
|
||||
defaultMessage: "Pay for subscription",
|
||||
})}
|
||||
/>
|
||||
{error && <b className="error">{mapSubscriptionErrorCode(error)}</b>}
|
||||
</>
|
||||
);
|
||||
}
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user