reorganize, fix some fast refresh warnings
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Martti Malmi 2024-01-10 18:00:26 +02:00
parent 071eed0d8c
commit baf6cc34ee
16 changed files with 172 additions and 169 deletions

View File

@ -35,3 +35,26 @@ export function useLocale() {
export const getLocale = () => { export const getLocale = () => {
return (navigator.languages && navigator.languages[0]) ?? navigator.language ?? DefaultLocale; return (navigator.languages && navigator.languages[0]) ?? navigator.language ?? DefaultLocale;
}; };
export const AllLanguageCodes = [
"en",
"ja",
"es",
"hu",
"zh-CN",
"zh-TW",
"fr",
"ar",
"it",
"id",
"de",
"ru",
"sv",
"hr",
"ta-IN",
"fa-IR",
"th",
"pt-BR",
"sw",
"nl",
"fi",
];

View File

@ -20,13 +20,14 @@ class TaskStore extends ExternalStore<Array<UITask>> {
constructor() { constructor() {
super(); super();
const AllTasks: Array<UITask> = [new BackupKeyTask(), new Nip5Task(), new DonateTask()]; const AllTasks: Array<UITask> = [new BackupKeyTask(), new Nip5Task()];
if (CONFIG.features.zapPool) { if (CONFIG.features.zapPool) {
AllTasks.push(new NoticeZapPoolDefault()); AllTasks.push(new NoticeZapPoolDefault());
} }
if (CONFIG.features.subscriptions) { if (CONFIG.features.subscriptions) {
AllTasks.push(new RenewSubTask()); AllTasks.push(new RenewSubTask());
} }
AllTasks.push(new DonateTask());
AllTasks.forEach(a => AllTasks.forEach(a =>
a.load(() => { a.load(() => {
this.notifyChange(); this.notifyChange();

View File

@ -20,11 +20,11 @@ import { useLoginRelays } from "@/Hooks/useLoginRelays";
import { transformTextCached } from "@/Hooks/useTextTransformCache"; import { transformTextCached } from "@/Hooks/useTextTransformCache";
import { useTheme } from "@/Hooks/useTheme"; import { useTheme } from "@/Hooks/useTheme";
import NavSidebar from "@/Pages/Layout/NavSidebar"; import NavSidebar from "@/Pages/Layout/NavSidebar";
import { mapPlanName } from "@/Pages/subscribe/utils";
import { trackEvent } from "@/Utils"; import { trackEvent } from "@/Utils";
import { getCurrentSubscription } from "@/Utils/Subscription"; import { getCurrentSubscription } from "@/Utils/Subscription";
import NotificationsPage from "./Notifications/Notifications"; import NotificationsPage from "./Notifications/Notifications";
import { mapPlanName } from "./subscribe";
type Cols = "notes" | "articles" | "media" | "streams" | "notifications"; type Cols = "notes" | "articles" | "media" | "streams" | "notifications";

View File

@ -2,12 +2,12 @@ import { unixNowMs } from "@snort/shared";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import Icon from "@/Components/Icons/Icon"; import Icon from "@/Components/Icons/Icon";
import { mapPlanName } from "@/Pages/subscribe/utils";
import { Birthday, Day } from "@/Utils/Const"; import { Birthday, Day } from "@/Utils/Const";
import useLogin from "../../Hooks/useLogin"; import useLogin from "../../Hooks/useLogin";
import { isBirthday, isChristmas, isHalloween, isStPatricksDay } from "../../Utils"; import { isBirthday, isChristmas, isHalloween, isStPatricksDay } from "../../Utils";
import { getCurrentSubscription } from "../../Utils/Subscription"; import { getCurrentSubscription } from "../../Utils/Subscription";
import { mapPlanName } from "../subscribe";
const getExtra = () => { const getExtra = () => {
if (isBirthday()) { if (isBirthday()) {

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import {useNavigate, useParams} from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Timeline from "@/Components/Feed/Timeline"; import Timeline from "@/Components/Feed/Timeline";
import UsersFeed from "@/Components/Feed/UsersFeed"; import UsersFeed from "@/Components/Feed/UsersFeed";

View File

@ -3,8 +3,7 @@ import "./index.css";
import { Outlet, RouteObject } from "react-router-dom"; import { Outlet, RouteObject } from "react-router-dom";
import Icon from "@/Components/Icons/Icon"; import Icon from "@/Components/Icons/Icon";
import { useLocale } from "@/Components/IntlProvider/IntlProviderUtils"; import { AllLanguageCodes, useLocale } from "@/Components/IntlProvider/IntlProviderUtils";
import { AllLanguageCodes } from "@/Pages/settings/Preferences";
import { Discover } from "./discover"; import { Discover } from "./discover";
import { Moderation } from "./moderation"; import { Moderation } from "./moderation";

View File

@ -2,7 +2,7 @@ import "./Preferences.css";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import { useLocale } from "@/Components/IntlProvider/IntlProviderUtils"; import { AllLanguageCodes, useLocale } from "@/Components/IntlProvider/IntlProviderUtils";
import useLogin from "@/Hooks/useLogin"; import useLogin from "@/Hooks/useLogin";
import { unwrap } from "@/Utils"; import { unwrap } from "@/Utils";
import { DefaultImgProxy } from "@/Utils/Const"; import { DefaultImgProxy } from "@/Utils/Const";
@ -10,30 +10,6 @@ import { updatePreferences, UserPreferences } from "@/Utils/Login";
import messages from "./messages"; import messages from "./messages";
export const AllLanguageCodes = [
"en",
"ja",
"es",
"hu",
"zh-CN",
"zh-TW",
"fr",
"ar",
"it",
"id",
"de",
"ru",
"sv",
"hr",
"ta-IN",
"fa-IR",
"th",
"pt-BR",
"sw",
"nl",
"fi",
];
const PreferencesPage = () => { const PreferencesPage = () => {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { id, perf } = useLogin(s => ({ id: s.id, perf: s.appData.item.preferences })); const { id, perf } = useLogin(s => ({ id: s.id, perf: s.appData.item.preferences }));

View File

@ -7,8 +7,7 @@ import AlbyIcon from "@/Components/Icons/Alby";
import BlueWallet from "@/Components/Icons/BlueWallet"; import BlueWallet from "@/Components/Icons/BlueWallet";
import Icon from "@/Components/Icons/Icon"; import Icon from "@/Components/Icons/Icon";
import NostrIcon from "@/Components/Icons/Nostrich"; import NostrIcon from "@/Components/Icons/Nostrich";
import { getAlbyOAuth } from "@/Pages/settings/wallet/utils";
import { getAlbyOAuth } from "./wallet/Alby";
const WalletRow = (props: { const WalletRow = (props: {
logo: ReactNode; logo: ReactNode;

View File

@ -1,12 +1,9 @@
import { sha256 } from "@noble/hashes/sha256";
import { randomBytes } from "@noble/hashes/utils";
import { base64, base64urlnopad, hex } from "@scure/base";
import { unixNow } from "@snort/shared";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { v4 as uuid } from "uuid"; import { v4 as uuid } from "uuid";
import PageSpinner from "@/Components/PageSpinner"; import PageSpinner from "@/Components/PageSpinner";
import { getAlbyOAuth } from "@/Pages/settings/wallet/utils";
import { WalletConfig, WalletKind, Wallets } from "@/Wallet"; import { WalletConfig, WalletKind, Wallets } from "@/Wallet";
import AlbyWallet from "@/Wallet/AlbyWallet"; import AlbyWallet from "@/Wallet/AlbyWallet";
@ -58,76 +55,3 @@ export default function AlbyOAuth() {
</> </>
); );
} }
export function getAlbyOAuth() {
const clientId = CONFIG.alby?.clientId ?? "";
const clientSecret = CONFIG.alby?.clientSecret ?? "";
const redirectUrl = `${window.location.protocol}//${window.location.host}/settings/wallet/alby`;
const scopes = [
"invoices:create",
"invoices:read",
"transactions:read",
"balance:read",
"payments:send",
"account:read",
];
const ec = new TextEncoder();
const tokenUrl = "https://api.getalby.com/oauth/token";
return {
tokenUrl,
getAuthUrl: () => {
const code_verifier = hex.encode(randomBytes(64));
window.sessionStorage.setItem("alby-code", code_verifier);
const params = new URLSearchParams();
params.set("client_id", clientId);
params.set("response_type", "code");
params.set("code_challenge", base64urlnopad.encode(sha256(code_verifier)));
params.set("code_challenge_method", "S256");
params.set("redirect_uri", redirectUrl);
params.set("scope", scopes.join(" "));
return `https://getalby.com/oauth?${params}`;
},
getToken: async (token: string) => {
const code = window.sessionStorage.getItem("alby-code");
if (!code) throw new Error("Alby code is missing!");
window.sessionStorage.removeItem("alby-code");
const form = new URLSearchParams();
form.set("client_id", clientId);
form.set("code_verifier", code);
form.set("grant_type", "authorization_code");
form.set("redirect_uri", redirectUrl);
form.set("code", token);
const req = await fetch(tokenUrl, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/x-www-form-urlencoded",
authorization: `Basic ${base64.encode(ec.encode(`${clientId}:${clientSecret}`))}`,
},
body: form,
});
const data = await req.json();
if (req.ok) {
return { ...data, created_at: unixNow() } as OAuthToken;
} else {
throw new Error(data.error_description as string);
}
},
};
}
export interface OAuthToken {
access_token: string;
created_at: number;
expires_in: number;
refresh_token: string;
scope: string;
token_type: string;
}

View File

@ -0,0 +1,77 @@
import { sha256 } from "@noble/hashes/sha256";
import { randomBytes } from "@noble/hashes/utils";
import { base64, base64urlnopad, hex } from "@scure/base";
import { unixNow } from "@snort/shared";
export function getAlbyOAuth() {
const clientId = CONFIG.alby?.clientId ?? "";
const clientSecret = CONFIG.alby?.clientSecret ?? "";
const redirectUrl = `${window.location.protocol}//${window.location.host}/settings/wallet/alby`;
const scopes = [
"invoices:create",
"invoices:read",
"transactions:read",
"balance:read",
"payments:send",
"account:read",
];
const ec = new TextEncoder();
const tokenUrl = "https://api.getalby.com/oauth/token";
return {
tokenUrl,
getAuthUrl: () => {
const code_verifier = hex.encode(randomBytes(64));
window.sessionStorage.setItem("alby-code", code_verifier);
const params = new URLSearchParams();
params.set("client_id", clientId);
params.set("response_type", "code");
params.set("code_challenge", base64urlnopad.encode(sha256(code_verifier)));
params.set("code_challenge_method", "S256");
params.set("redirect_uri", redirectUrl);
params.set("scope", scopes.join(" "));
return `https://getalby.com/oauth?${params}`;
},
getToken: async (token: string) => {
const code = window.sessionStorage.getItem("alby-code");
if (!code) throw new Error("Alby code is missing!");
window.sessionStorage.removeItem("alby-code");
const form = new URLSearchParams();
form.set("client_id", clientId);
form.set("code_verifier", code);
form.set("grant_type", "authorization_code");
form.set("redirect_uri", redirectUrl);
form.set("code", token);
const req = await fetch(tokenUrl, {
method: "POST",
headers: {
accept: "application/json",
"content-type": "application/x-www-form-urlencoded",
authorization: `Basic ${base64.encode(ec.encode(`${clientId}:${clientSecret}`))}`,
},
body: form,
});
const data = await req.json();
if (req.ok) {
return { ...data, created_at: unixNow() } as OAuthToken;
} else {
throw new Error(data.error_description as string);
}
},
};
}
export interface OAuthToken {
access_token: string;
created_at: number;
expires_in: number;
refresh_token: string;
scope: string;
token_type: string;
}

View File

@ -6,8 +6,8 @@ import { ErrorOrOffline } from "@/Components/ErrorOrOffline";
import PageSpinner from "@/Components/PageSpinner"; import PageSpinner from "@/Components/PageSpinner";
import SnortApi, { Subscription, SubscriptionError } from "@/External/SnortApi"; import SnortApi, { Subscription, SubscriptionError } from "@/External/SnortApi";
import useEventPublisher from "@/Hooks/useEventPublisher"; import useEventPublisher from "@/Hooks/useEventPublisher";
import { mapSubscriptionErrorCode } from "@/Pages/subscribe/utils";
import { mapSubscriptionErrorCode } from ".";
import SubscriptionCard from "./SubscriptionCard"; import SubscriptionCard from "./SubscriptionCard";
export default function ManageSubscriptionPage() { export default function ManageSubscriptionPage() {

View File

@ -7,10 +7,9 @@ import SendSats from "@/Components/SendSats/SendSats";
import SnortApi, { Subscription, SubscriptionError } from "@/External/SnortApi"; import SnortApi, { Subscription, SubscriptionError } from "@/External/SnortApi";
import useEventPublisher from "@/Hooks/useEventPublisher"; import useEventPublisher from "@/Hooks/useEventPublisher";
import useLogin from "@/Hooks/useLogin"; import useLogin from "@/Hooks/useLogin";
import { mapPlanName, mapSubscriptionErrorCode } from "@/Pages/subscribe/utils";
import { mostRecentSubscription } from "@/Utils/Subscription"; import { mostRecentSubscription } from "@/Utils/Subscription";
import { mapPlanName, mapSubscriptionErrorCode } from ".";
export function RenewSub({ sub: s }: { sub?: Subscription }) { export function RenewSub({ sub: s }: { sub?: Subscription }) {
const { subscriptions } = useLogin(s => ({ subscriptions: s.subscriptions })); const { subscriptions } = useLogin(s => ({ subscriptions: s.subscriptions }));
const { publisher } = useEventPublisher(); const { publisher } = useEventPublisher();

View File

@ -5,8 +5,8 @@ import Nip5Service from "@/Components/Nip5Service";
import Nip05 from "@/Components/User/Nip05"; import Nip05 from "@/Components/User/Nip05";
import { Subscription } from "@/External/SnortApi"; import { Subscription } from "@/External/SnortApi";
import { SnortNostrAddressService } from "@/Pages/NostrAddressPage"; import { SnortNostrAddressService } from "@/Pages/NostrAddressPage";
import { mapPlanName } from "@/Pages/subscribe/utils";
import { mapPlanName } from ".";
import { RenewSub } from "./RenewSub"; import { RenewSub } from "./RenewSub";
export default function SubscriptionCard({ sub }: { sub: Subscription }) { export default function SubscriptionCard({ sub }: { sub: Subscription }) {

View File

@ -7,64 +7,13 @@ import { RouteObject } from "react-router-dom";
import AsyncButton from "@/Components/Button/AsyncButton"; import AsyncButton from "@/Components/Button/AsyncButton";
import SendSats from "@/Components/SendSats/SendSats"; import SendSats from "@/Components/SendSats/SendSats";
import SnortApi, { SubscriptionError, SubscriptionErrorCode } from "@/External/SnortApi"; import SnortApi, { SubscriptionError } from "@/External/SnortApi";
import useEventPublisher from "@/Hooks/useEventPublisher"; import useEventPublisher from "@/Hooks/useEventPublisher";
import ManageSubscriptionPage from "@/Pages/subscribe/ManageSubscription"; import ManageSubscriptionPage from "@/Pages/subscribe/ManageSubscription";
import { mapFeatureName, mapPlanName, mapSubscriptionErrorCode } from "@/Pages/subscribe/utils";
import { getRefCode } from "@/Utils"; import { getRefCode } from "@/Utils";
import { formatShort } from "@/Utils/Number"; import { formatShort } from "@/Utils/Number";
import { LockedFeatures, Plans, SubscriptionType } from "@/Utils/Subscription"; import { Plans } from "@/Utils/Subscription";
export function mapPlanName(id: number) {
switch (id) {
case SubscriptionType.Supporter:
return <FormattedMessage defaultMessage="FAN" id="xybOUv" />;
case SubscriptionType.Premium:
return <FormattedMessage defaultMessage="PRO" id="hRTfTR" />;
}
}
export function mapFeatureName(k: LockedFeatures) {
switch (k) {
case LockedFeatures.MultiAccount:
return <FormattedMessage defaultMessage="Multi account support" id="cuP16y" />;
case LockedFeatures.NostrAddress:
return <FormattedMessage defaultMessage="Snort nostr address" id="lPWASz" />;
case LockedFeatures.Badge:
return <FormattedMessage defaultMessage="Supporter Badge" id="ttxS0b" />;
case LockedFeatures.DeepL:
return <FormattedMessage defaultMessage="DeepL translations" id="iEoXYx" />;
case LockedFeatures.RelayRetention:
return <FormattedMessage defaultMessage="Unlimited note retention on Snort relay" id="Ai8VHU" />;
case LockedFeatures.RelayBackup:
return <FormattedMessage defaultMessage="Downloadable backups from Snort relay" id="pI+77w" />;
case LockedFeatures.RelayAccess:
return (
<FormattedMessage defaultMessage="Write access to Snort relay, with 1 year of event retention" id="BGCM48" />
);
case LockedFeatures.LNProxy:
return <FormattedMessage defaultMessage="LN Address Proxy" id="SYQtZ7" />;
case LockedFeatures.EmailBridge:
return <FormattedMessage defaultMessage="Email <> DM bridge for your Snort nostr address" id="qD9EUF" />;
}
}
export function mapSubscriptionErrorCode(c: SubscriptionError) {
switch (c.code) {
case SubscriptionErrorCode.InternalError:
return <FormattedMessage defaultMessage="Internal error: {msg}" id="jMzO1S" values={{ msg: c.message }} />;
case SubscriptionErrorCode.SubscriptionActive:
return <FormattedMessage defaultMessage="You subscription is still active, you can't renew yet" id="OQXnew" />;
case SubscriptionErrorCode.Duplicate:
return (
<FormattedMessage
defaultMessage="You already have a subscription of this type, please renew or pay"
id="NAuFNH"
/>
);
default:
return c.message;
}
}
export function SubscribePage() { export function SubscribePage() {
const { publisher } = useEventPublisher(); const { publisher } = useEventPublisher();

View File

@ -0,0 +1,56 @@
import { FormattedMessage } from "react-intl";
import { SubscriptionError, SubscriptionErrorCode } from "@/External/SnortApi";
import { LockedFeatures, SubscriptionType } from "@/Utils/Subscription";
export function mapPlanName(id: number) {
switch (id) {
case SubscriptionType.Supporter:
return <FormattedMessage defaultMessage="FAN" id="xybOUv" />;
case SubscriptionType.Premium:
return <FormattedMessage defaultMessage="PRO" id="hRTfTR" />;
}
}
export function mapFeatureName(k: LockedFeatures) {
switch (k) {
case LockedFeatures.MultiAccount:
return <FormattedMessage defaultMessage="Multi account support" id="cuP16y" />;
case LockedFeatures.NostrAddress:
return <FormattedMessage defaultMessage="Snort nostr address" id="lPWASz" />;
case LockedFeatures.Badge:
return <FormattedMessage defaultMessage="Supporter Badge" id="ttxS0b" />;
case LockedFeatures.DeepL:
return <FormattedMessage defaultMessage="DeepL translations" id="iEoXYx" />;
case LockedFeatures.RelayRetention:
return <FormattedMessage defaultMessage="Unlimited note retention on Snort relay" id="Ai8VHU" />;
case LockedFeatures.RelayBackup:
return <FormattedMessage defaultMessage="Downloadable backups from Snort relay" id="pI+77w" />;
case LockedFeatures.RelayAccess:
return (
<FormattedMessage defaultMessage="Write access to Snort relay, with 1 year of event retention" id="BGCM48" />
);
case LockedFeatures.LNProxy:
return <FormattedMessage defaultMessage="LN Address Proxy" id="SYQtZ7" />;
case LockedFeatures.EmailBridge:
return <FormattedMessage defaultMessage="Email <> DM bridge for your Snort nostr address" id="qD9EUF" />;
}
}
export function mapSubscriptionErrorCode(c: SubscriptionError) {
switch (c.code) {
case SubscriptionErrorCode.InternalError:
return <FormattedMessage defaultMessage="Internal error: {msg}" id="jMzO1S" values={{ msg: c.message }} />;
case SubscriptionErrorCode.SubscriptionActive:
return <FormattedMessage defaultMessage="You subscription is still active, you can't renew yet" id="OQXnew" />;
case SubscriptionErrorCode.Duplicate:
return (
<FormattedMessage
defaultMessage="You already have a subscription of this type, please renew or pay"
id="NAuFNH"
/>
);
default:
return c.message;
}
}

View File

@ -1,7 +1,7 @@
import { base64 } from "@scure/base"; import { base64 } from "@scure/base";
import { unixNow, unwrap } from "@snort/shared"; import { unixNow, unwrap } from "@snort/shared";
import { OAuthToken } from "@/Pages/settings/wallet/Alby"; import { OAuthToken } from "@/Pages/settings/wallet/utils";
import { import {
InvoiceRequest, InvoiceRequest,