@ -16,7 +16,6 @@
|
|||||||
"@sqlite.org/sqlite-wasm": "^3.45.1-build1",
|
"@sqlite.org/sqlite-wasm": "^3.45.1-build1",
|
||||||
"@szhsin/react-menu": "^4.1.0",
|
"@szhsin/react-menu": "^4.1.0",
|
||||||
"@types/webscopeio__react-textarea-autocomplete": "^4.7.5",
|
"@types/webscopeio__react-textarea-autocomplete": "^4.7.5",
|
||||||
"@void-cat/api": "^1.0.12",
|
|
||||||
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
"@webscopeio/react-textarea-autocomplete": "^4.9.2",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
|
@ -1,67 +1,57 @@
|
|||||||
import { VoidApi } from "@void-cat/api";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { Layer2Button } from "./buttons";
|
import { Layer2Button } from "./buttons";
|
||||||
import { openFile } from "@/utils";
|
import { openFile } from "@/utils";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
|
import { useMediaServerList } from "@/hooks/media-servers";
|
||||||
const voidCatHost = "https://void.cat";
|
import { Nip96Server } from "@/service/upload/nip96";
|
||||||
const fileExtensionRegex = /\.([\w]{1,7})$/i;
|
import { useLogin } from "@/hooks/login";
|
||||||
const voidCatApi = new VoidApi(voidCatHost);
|
import { ReactNode } from "react";
|
||||||
|
import { EventPublisher } from "@snort/system";
|
||||||
type UploadResult = {
|
|
||||||
url?: string;
|
|
||||||
error?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function voidCatUpload(file: File): Promise<UploadResult> {
|
|
||||||
const uploader = voidCatApi.getUploader(file);
|
|
||||||
|
|
||||||
const rsp = await uploader.upload({
|
|
||||||
"V-Strip-Metadata": "true",
|
|
||||||
});
|
|
||||||
if (rsp.ok) {
|
|
||||||
let ext = file.name.match(fileExtensionRegex);
|
|
||||||
if (rsp.file?.metadata?.mimeType === "image/webp") {
|
|
||||||
ext = ["", "webp"];
|
|
||||||
}
|
|
||||||
const resultUrl = rsp.file?.metadata?.url ?? `${voidCatHost}/d/${rsp.file?.id}${ext ? `.${ext[1]}` : ""}`;
|
|
||||||
|
|
||||||
const ret = {
|
|
||||||
url: resultUrl,
|
|
||||||
} as UploadResult;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
error: rsp.errorMessage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface FileUploaderProps {
|
interface FileUploaderProps {
|
||||||
onResult(url: string | undefined): void;
|
onResult(url: string | undefined): void;
|
||||||
|
onError(e: string | Error): void;
|
||||||
|
children?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
publisher?: EventPublisher;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FileUploader({ onResult }: FileUploaderProps) {
|
export function FileUploader({ onResult, onError, children, className, publisher }: FileUploaderProps) {
|
||||||
async function uploadFile() {
|
const servers = useMediaServerList();
|
||||||
|
const pub = publisher ?? useLogin()?.publisher?.();
|
||||||
|
|
||||||
|
async function uploadFile(e: React.MouseEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
const file = await openFile();
|
const file = await openFile();
|
||||||
if (file) {
|
if (file && pub) {
|
||||||
try {
|
try {
|
||||||
const upload = await voidCatUpload(file);
|
const server = new Nip96Server(servers.servers[0], pub);
|
||||||
|
const upload = await server.upload(file, file.name);
|
||||||
if (upload.url) {
|
if (upload.url) {
|
||||||
onResult(upload.url);
|
onResult(upload.url);
|
||||||
}
|
}
|
||||||
if (upload.error) {
|
if (upload.error) {
|
||||||
console.error(upload.error);
|
onError(upload.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
if (error instanceof Error) {
|
||||||
|
onError(error);
|
||||||
|
} else {
|
||||||
|
onError(new Error("Unknown error"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (children) {
|
||||||
|
return (
|
||||||
|
<div onClick={uploadFile} className={className}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Layer2Button onClick={uploadFile}>
|
<Layer2Button onClick={uploadFile} className={className}>
|
||||||
<FormattedMessage defaultMessage="Upload" />
|
<FormattedMessage defaultMessage="Upload" />
|
||||||
<Icon name="upload" size={14} />
|
<Icon name="upload" size={14} />
|
||||||
</Layer2Button>
|
</Layer2Button>
|
||||||
|
@ -11,21 +11,20 @@ import LoginWallet2x from "../login-wallet@2x.jpg";
|
|||||||
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
|
||||||
import { EventPublisher, UserMetadata } from "@snort/system";
|
import { EventPublisher, PrivateKeySigner, UserMetadata } from "@snort/system";
|
||||||
import { schnorr } from "@noble/curves/secp256k1";
|
import { schnorr } from "@noble/curves/secp256k1";
|
||||||
import { bytesToHex } from "@noble/curves/abstract/utils";
|
import { bytesToHex } from "@noble/curves/abstract/utils";
|
||||||
import { LNURL, bech32ToHex, getPublicKey, hexToBech32 } from "@snort/shared";
|
import { LNURL, bech32ToHex, getPublicKey, hexToBech32 } from "@snort/shared";
|
||||||
import { VoidApi } from "@void-cat/api";
|
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
import { Login, LoginType } from "@/login";
|
import { Login, LoginType } from "@/login";
|
||||||
import { Icon } from "./icon";
|
import { Icon } from "./icon";
|
||||||
import Copy from "./copy";
|
import Copy from "./copy";
|
||||||
import { openFile } from "@/utils";
|
|
||||||
import { DefaultProvider, StreamProviderInfo } from "@/providers";
|
import { DefaultProvider, StreamProviderInfo } from "@/providers";
|
||||||
import { NostrStreamProvider } from "@/providers/zsz";
|
import { NostrStreamProvider } from "@/providers/zsz";
|
||||||
import { DefaultButton, Layer1Button } from "./buttons";
|
import { DefaultButton, Layer1Button } from "./buttons";
|
||||||
import { ExternalLink } from "./external-link";
|
import { ExternalLink } from "./external-link";
|
||||||
|
import { FileUploader } from "./file-uploader";
|
||||||
|
|
||||||
enum Stage {
|
enum Stage {
|
||||||
Login = 0,
|
Login = 0,
|
||||||
@ -46,6 +45,7 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
const [key, setNewKey] = useState("");
|
const [key, setNewKey] = useState("");
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const hasNostrExtension = "nostr" in window && window.nostr;
|
const hasNostrExtension = "nostr" in window && window.nostr;
|
||||||
|
const signer = key ? new PrivateKeySigner(key) : undefined;
|
||||||
|
|
||||||
function doLoginNsec() {
|
function doLoginNsec() {
|
||||||
try {
|
try {
|
||||||
@ -89,34 +89,6 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadAvatar() {
|
|
||||||
const defaultError = formatMessage({
|
|
||||||
defaultMessage: "Avatar upload fialed",
|
|
||||||
id: "uTonxS",
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const file = await openFile();
|
|
||||||
if (file) {
|
|
||||||
const VoidCatHost = "https://void.cat";
|
|
||||||
const api = new VoidApi(VoidCatHost);
|
|
||||||
const uploader = api.getUploader(file);
|
|
||||||
const result = await uploader.upload({
|
|
||||||
"V-Strip-Metadata": "true",
|
|
||||||
});
|
|
||||||
console.debug(result);
|
|
||||||
if (result.ok) {
|
|
||||||
const resultUrl = result.file?.metadata?.url ?? `${VoidCatHost}/d/${result.file?.id}`;
|
|
||||||
setAvatar(resultUrl);
|
|
||||||
} else {
|
|
||||||
setError(result.errorMessage ?? defaultError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setError(defaultError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupProfile() {
|
async function setupProfile() {
|
||||||
const px = new NostrStreamProvider(DefaultProvider.name, DefaultProvider.url, EventPublisher.privateKey(key));
|
const px = new NostrStreamProvider(DefaultProvider.name, DefaultProvider.url, EventPublisher.privateKey(key));
|
||||||
const info = await px.info();
|
const info = await px.info();
|
||||||
@ -248,13 +220,17 @@ export function LoginSignup({ close }: { close: () => void }) {
|
|||||||
<h2>
|
<h2>
|
||||||
<FormattedMessage defaultMessage="Setup Profile" />
|
<FormattedMessage defaultMessage="Setup Profile" />
|
||||||
</h2>
|
</h2>
|
||||||
<div className="relative mx-auto w-[100px] h-[100px] rounded-full overflow-hidden">
|
<div className="relative mx-auto w-[100px] h-[100px] rounded-full overflow-hidden bg-layer-3">
|
||||||
{avatar && <img className="absolute object-fit w-full h-full" src={avatar} />}
|
{avatar && <img className="absolute object-fit w-full h-full" src={avatar} />}
|
||||||
<div
|
{signer && (
|
||||||
className="absolute flex items-center justify-center w-full h-full hover:opacity-100 opacity-0 transition bg-layer-2/50 cursor-pointer"
|
<FileUploader
|
||||||
onClick={uploadAvatar}>
|
publisher={new EventPublisher(signer, signer.getPubKey())}
|
||||||
<Icon name="camera-plus" />
|
onResult={e => setAvatar(e ?? "")}
|
||||||
</div>
|
onError={e => setError(e.toString())}
|
||||||
|
className="absolute flex items-center justify-center w-full h-full hover:opacity-30 opacity-0 transition bg-black cursor-pointer">
|
||||||
|
<Icon name="camera-plus" />
|
||||||
|
</FileUploader>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -4,9 +4,9 @@ import { LNURL, fetchNip05Pubkey } from "@snort/shared";
|
|||||||
import { mapEventToProfile } from "@snort/system";
|
import { mapEventToProfile } from "@snort/system";
|
||||||
import { SnortContext, useUserProfile } from "@snort/system-react";
|
import { SnortContext, useUserProfile } from "@snort/system-react";
|
||||||
import { useLogin } from "@/hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
import { debounce, openFile } from "@/utils";
|
import { debounce } from "@/utils";
|
||||||
import { PrimaryButton } from "./buttons";
|
import { PrimaryButton } from "./buttons";
|
||||||
import { VoidApi } from "@void-cat/api";
|
import { FileUploader } from "./file-uploader";
|
||||||
|
|
||||||
const MaxUsernameLength = 100;
|
const MaxUsernameLength = 100;
|
||||||
const MaxAboutLength = 500;
|
const MaxAboutLength = 500;
|
||||||
@ -116,35 +116,6 @@ export function ProfileEditor({ onClose }: { onClose: () => void }) {
|
|||||||
});
|
});
|
||||||
}, [formatMessage, login?.pubkey, nip05]);
|
}, [formatMessage, login?.pubkey, nip05]);
|
||||||
|
|
||||||
async function uploadAvatar() {
|
|
||||||
const defaultError = formatMessage({
|
|
||||||
defaultMessage: "Avatar upload fialed",
|
|
||||||
id: "uTonxS",
|
|
||||||
});
|
|
||||||
|
|
||||||
setError(undefined);
|
|
||||||
try {
|
|
||||||
const file = await openFile();
|
|
||||||
if (file) {
|
|
||||||
const VoidCatHost = "https://void.cat";
|
|
||||||
const api = new VoidApi(VoidCatHost);
|
|
||||||
const uploader = api.getUploader(file);
|
|
||||||
const result = await uploader.upload({
|
|
||||||
"V-Strip-Metadata": "true",
|
|
||||||
});
|
|
||||||
console.debug(result);
|
|
||||||
if (result.ok) {
|
|
||||||
const resultUrl = result.file?.metadata?.url ?? `${VoidCatHost}/d/${result.file?.id}`;
|
|
||||||
setPicture(resultUrl);
|
|
||||||
} else {
|
|
||||||
setError(new Error(result.errorMessage ?? defaultError));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setError(new Error(defaultError));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveProfile() {
|
async function saveProfile() {
|
||||||
// copy user object and delete internal fields
|
// copy user object and delete internal fields
|
||||||
const userCopy = {
|
const userCopy = {
|
||||||
@ -225,11 +196,12 @@ export function ProfileEditor({ onClose }: { onClose: () => void }) {
|
|||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="mx-auto relative flex items-center justify-center w-40 h-40 aspect-square rounded-full overflow-hidden">
|
<div className="mx-auto relative flex items-center justify-center w-40 h-40 aspect-square rounded-full overflow-hidden">
|
||||||
<img className="absolute w-full h-full object-cover" src={picture} />
|
<img className="absolute w-full h-full object-cover" src={picture} />
|
||||||
<div
|
<FileUploader
|
||||||
className="flex items-center justify-center absolute w-full h-full opacity-0 hover:opacity-80 bg-foreground cursor-pointer"
|
onResult={e => setPicture(e ?? "")}
|
||||||
onClick={() => uploadAvatar()}>
|
onError={e => setError(e instanceof Error ? e : new Error(e))}
|
||||||
|
className="flex items-center justify-center absolute w-full h-full opacity-0 hover:opacity-80 bg-foreground cursor-pointer">
|
||||||
<FormattedMessage defaultMessage="Edit" />
|
<FormattedMessage defaultMessage="Edit" />
|
||||||
</div>
|
</FileUploader>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col w-full gap-2">
|
<div className="flex flex-col w-full gap-2">
|
||||||
<h4>
|
<h4>
|
||||||
|
@ -47,6 +47,7 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
const [goalAmount, setGoalMount] = useState(0);
|
const [goalAmount, setGoalMount] = useState(0);
|
||||||
const [game, setGame] = useState<GameInfo>();
|
const [game, setGame] = useState<GameInfo>();
|
||||||
const [gameId, setGameId] = useState<string>();
|
const [gameId, setGameId] = useState<string>();
|
||||||
|
const [error, setError] = useState("");
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
@ -180,8 +181,9 @@ export function StreamEditor({ ev, onFinish, options }: StreamEditorProps) {
|
|||||||
{image && <img src={image} className="mb-2 aspect-video object-cover rounded-xl" />}
|
{image && <img src={image} className="mb-2 aspect-video object-cover rounded-xl" />}
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
<input type="text" placeholder="https://" value={image} onChange={e => setImage(e.target.value)} />
|
||||||
<FileUploader onResult={v => setImage(v ?? "")} />
|
<FileUploader onResult={v => setImage(v ?? "")} onError={e => setError(e.toString())} />
|
||||||
</div>
|
</div>
|
||||||
|
{error && <b className="text-warning">{error}</b>}
|
||||||
</StreamInput>
|
</StreamInput>
|
||||||
)}
|
)}
|
||||||
{(options?.canSetStream ?? true) && (
|
{(options?.canSetStream ?? true) && (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useMediaServerList } from "@/hooks/media-servers";
|
import { DefaultMediaServers, useMediaServerList } from "@/hooks/media-servers";
|
||||||
import { IconButton, PrimaryButton } from "../buttons";
|
import { IconButton, PrimaryButton } from "../buttons";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -6,13 +6,6 @@ import { sanitizeRelayUrl } from "@snort/shared";
|
|||||||
|
|
||||||
export function ServerList() {
|
export function ServerList() {
|
||||||
const [newServer, setNewServer] = useState("");
|
const [newServer, setNewServer] = useState("");
|
||||||
|
|
||||||
const defaultServers = [
|
|
||||||
//"https://media.zap.stream",
|
|
||||||
"https://nostr.build/",
|
|
||||||
"https://nostrcheck.me/",
|
|
||||||
"https://files.v0l.io/",
|
|
||||||
];
|
|
||||||
const servers = useMediaServerList();
|
const servers = useMediaServerList();
|
||||||
|
|
||||||
async function tryAddServer(s: string) {
|
async function tryAddServer(s: string) {
|
||||||
@ -51,7 +44,7 @@ export function ServerList() {
|
|||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage defaultMessage="Suggested Servers" />
|
<FormattedMessage defaultMessage="Suggested Servers" />
|
||||||
</h4>
|
</h4>
|
||||||
{defaultServers
|
{DefaultMediaServers.map(a => a.value[1])
|
||||||
.filter(a => !servers.servers.includes(a))
|
.filter(a => !servers.servers.includes(a))
|
||||||
.map(a => (
|
.map(a => (
|
||||||
<div className="flex items-center justify-between py-2 px-3 bg-layer-2 rounded-xl" key={a}>
|
<div className="flex items-center justify-between py-2 px-3 bg-layer-2 rounded-xl" key={a}>
|
||||||
|
@ -4,10 +4,20 @@ import { removeUndefined, sanitizeRelayUrl } from "@snort/shared";
|
|||||||
import { Nip96Server } from "@/service/upload/nip96";
|
import { Nip96Server } from "@/service/upload/nip96";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export const DefaultMediaServers = [
|
||||||
|
//"https://media.zap.stream",
|
||||||
|
new UnknownTag(["server", "https://nostr.build/"]),
|
||||||
|
new UnknownTag(["server", "https://nostrcheck.me/"]),
|
||||||
|
new UnknownTag(["server", "https://files.v0l.io/"]),
|
||||||
|
];
|
||||||
|
|
||||||
export function useMediaServerList() {
|
export function useMediaServerList() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
|
|
||||||
const servers = login?.state?.getList(EventKind.StorageServerList) ?? [];
|
let servers = login?.state?.getList(EventKind.StorageServerList) ?? [];
|
||||||
|
if (servers.length === 0) {
|
||||||
|
servers = DefaultMediaServers;
|
||||||
|
}
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
@ -906,9 +906,6 @@
|
|||||||
"u6uD94": {
|
"u6uD94": {
|
||||||
"defaultMessage": "Create an Account"
|
"defaultMessage": "Create an Account"
|
||||||
},
|
},
|
||||||
"uTonxS": {
|
|
||||||
"defaultMessage": "Avatar upload fialed"
|
|
||||||
},
|
|
||||||
"uYw2LD": {
|
"uYw2LD": {
|
||||||
"defaultMessage": "Stream"
|
"defaultMessage": "Stream"
|
||||||
},
|
},
|
||||||
|
@ -26,7 +26,7 @@ export default function DashboardIntroFinal() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="mx-auto flex flex-col items-center">
|
||||||
<StepHeader />
|
<StepHeader />
|
||||||
<div className="flex flex-col gap-4 w-[30rem]">
|
<div className="flex flex-col gap-4 w-[30rem]">
|
||||||
<h2 className="text-center">
|
<h2 className="text-center">
|
||||||
|
@ -12,6 +12,7 @@ export default function DashboardIntroStep1() {
|
|||||||
const [title, setTitle] = useState<string>();
|
const [title, setTitle] = useState<string>();
|
||||||
const [summary, setDescription] = useState<string>();
|
const [summary, setDescription] = useState<string>();
|
||||||
const [image, setImage] = useState<string>();
|
const [image, setImage] = useState<string>();
|
||||||
|
const [error, setError] = useState<string>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
DefaultProvider.info().then(i => {
|
DefaultProvider.info().then(i => {
|
||||||
@ -48,8 +49,9 @@ export default function DashboardIntroStep1() {
|
|||||||
onChange={e => setImage(e.target.value)}
|
onChange={e => setImage(e.target.value)}
|
||||||
placeholder={formatMessage({ defaultMessage: "Cover image URL (optional)" })}
|
placeholder={formatMessage({ defaultMessage: "Cover image URL (optional)" })}
|
||||||
/>
|
/>
|
||||||
<FileUploader onResult={setImage} />
|
<FileUploader onResult={setImage} onError={e => setError(e.toString())} />
|
||||||
</div>
|
</div>
|
||||||
|
{error && <b className="text-warning">{error}</b>}
|
||||||
<small className="text-layer-4">
|
<small className="text-layer-4">
|
||||||
<FormattedMessage defaultMessage="Recommended size: 1920x1080 (16:9)" />
|
<FormattedMessage defaultMessage="Recommended size: 1920x1080 (16:9)" />
|
||||||
</small>
|
</small>
|
||||||
|
@ -3,11 +3,12 @@ import StepHeader from "./step-header";
|
|||||||
import { DefaultButton } from "@/element/buttons";
|
import { DefaultButton } from "@/element/buttons";
|
||||||
import { DefaultProvider, StreamProviderForward } from "@/providers";
|
import { DefaultProvider, StreamProviderForward } from "@/providers";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { AddForwardInputs } from "@/element/provider/nostr/fowards";
|
import { AddForwardInputs } from "@/element/provider/nostr/fowards";
|
||||||
|
|
||||||
export default function DashboardIntroStep3() {
|
export default function DashboardIntroStep3() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const [forwards, setForwards] = useState<Array<StreamProviderForward>>([]);
|
const [forwards, setForwards] = useState<Array<StreamProviderForward>>([]);
|
||||||
|
|
||||||
async function loadInfo() {
|
async function loadInfo() {
|
||||||
@ -21,7 +22,7 @@ export default function DashboardIntroStep3() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="mx-auto flex flex-col items-center">
|
||||||
<StepHeader />
|
<StepHeader />
|
||||||
<div className="flex flex-col gap-4 w-[30rem]">
|
<div className="flex flex-col gap-4 w-[30rem]">
|
||||||
<h2 className="text-center">
|
<h2 className="text-center">
|
||||||
@ -50,7 +51,9 @@ export default function DashboardIntroStep3() {
|
|||||||
<AddForwardInputs provider={DefaultProvider} onAdd={loadInfo} />
|
<AddForwardInputs provider={DefaultProvider} onAdd={loadInfo} />
|
||||||
<DefaultButton
|
<DefaultButton
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
navigate("/dashboard/step-4");
|
navigate("/dashboard/step-4", {
|
||||||
|
state: location.state,
|
||||||
|
});
|
||||||
}}>
|
}}>
|
||||||
<FormattedMessage defaultMessage="Continue" />
|
<FormattedMessage defaultMessage="Continue" />
|
||||||
</DefaultButton>
|
</DefaultButton>
|
||||||
|
@ -3,7 +3,7 @@ import StepHeader from "./step-header";
|
|||||||
import { DefaultButton } from "@/element/buttons";
|
import { DefaultButton } from "@/element/buttons";
|
||||||
import { DefaultProvider } from "@/providers";
|
import { DefaultProvider } from "@/providers";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { GoalSelector } from "@/element/stream-editor/goal-selector";
|
import { GoalSelector } from "@/element/stream-editor/goal-selector";
|
||||||
import AmountInput from "@/element/amount-input";
|
import AmountInput from "@/element/amount-input";
|
||||||
import { useLogin } from "@/hooks/login";
|
import { useLogin } from "@/hooks/login";
|
||||||
@ -12,6 +12,7 @@ import { SnortContext } from "@snort/system-react";
|
|||||||
|
|
||||||
export default function DashboardIntroStep4() {
|
export default function DashboardIntroStep4() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
const [goalName, setGoalName] = useState("");
|
const [goalName, setGoalName] = useState("");
|
||||||
const [goalAmount, setGoalMount] = useState(0);
|
const [goalAmount, setGoalMount] = useState(0);
|
||||||
const [goal, setGoal] = useState<string>();
|
const [goal, setGoal] = useState<string>();
|
||||||
@ -30,7 +31,7 @@ export default function DashboardIntroStep4() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center">
|
<div className="mx-auto flex flex-col items-center">
|
||||||
<StepHeader />
|
<StepHeader />
|
||||||
<div className="flex flex-col gap-4 w-[30rem]">
|
<div className="flex flex-col gap-4 w-[30rem]">
|
||||||
<h2 className="text-center">
|
<h2 className="text-center">
|
||||||
@ -67,15 +68,27 @@ export default function DashboardIntroStep4() {
|
|||||||
.content(goalName);
|
.content(goalName);
|
||||||
});
|
});
|
||||||
await system.BroadcastEvent(goalEvent);
|
await system.BroadcastEvent(goalEvent);
|
||||||
await DefaultProvider.updateStream({
|
const newState = {
|
||||||
|
...location.state,
|
||||||
goal: goalEvent.id,
|
goal: goalEvent.id,
|
||||||
|
};
|
||||||
|
await DefaultProvider.updateStream(newState);
|
||||||
|
navigate("/dashboard/final", {
|
||||||
|
state: newState,
|
||||||
});
|
});
|
||||||
navigate("/dashboard/final");
|
|
||||||
} else if (goal) {
|
} else if (goal) {
|
||||||
await DefaultProvider.updateStream({
|
const newState = {
|
||||||
|
...location.state,
|
||||||
goal,
|
goal,
|
||||||
|
};
|
||||||
|
await DefaultProvider.updateStream(newState);
|
||||||
|
navigate("/dashboard/final", {
|
||||||
|
state: newState,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigate("/dashboard/final", {
|
||||||
|
state: location.state,
|
||||||
});
|
});
|
||||||
navigate("/dashboard/final");
|
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<FormattedMessage defaultMessage="Continue" />
|
<FormattedMessage defaultMessage="Continue" />
|
||||||
|
@ -298,7 +298,6 @@
|
|||||||
"twCRVi": "Art",
|
"twCRVi": "Art",
|
||||||
"tzMNF3": "Status",
|
"tzMNF3": "Status",
|
||||||
"u6uD94": "Create an Account",
|
"u6uD94": "Create an Account",
|
||||||
"uTonxS": "Avatar upload fialed",
|
|
||||||
"uYw2LD": "Stream",
|
"uYw2LD": "Stream",
|
||||||
"uksRSi": "Latest Videos",
|
"uksRSi": "Latest Videos",
|
||||||
"vP4dFa": "Visit {link} to get some sweet zap.stream merch!",
|
"vP4dFa": "Visit {link} to get some sweet zap.stream merch!",
|
||||||
|
17
yarn.lock
17
yarn.lock
@ -3245,15 +3245,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@void-cat/api@npm:^1.0.12":
|
|
||||||
version: 1.0.12
|
|
||||||
resolution: "@void-cat/api@npm:1.0.12"
|
|
||||||
dependencies:
|
|
||||||
sjcl: "npm:^1.0.8"
|
|
||||||
checksum: 10c0/37d67e077a1ece0509a669d2ebd5a1edc26773b25417287ce333e0f854ae1c073ea3ca4e311c137faf2b9c0985a0ae630e52cbdd6f431cfe1691d42d2ebe0acc
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@webbtc/webln-types@npm:^3.0.0":
|
"@webbtc/webln-types@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "@webbtc/webln-types@npm:3.0.0"
|
resolution: "@webbtc/webln-types@npm:3.0.0"
|
||||||
@ -7502,13 +7493,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"sjcl@npm:^1.0.8":
|
|
||||||
version: 1.0.8
|
|
||||||
resolution: "sjcl@npm:1.0.8"
|
|
||||||
checksum: 10c0/deac03b931a26b710a521fc9dd5a10b124fb63970b7f5e22a91460ac46b14dd4ccfc2edbee4d553b8d586da332d9013a9ff40f5adfdb58050b73ebcd90235cba
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"slash@npm:^3.0.0":
|
"slash@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "slash@npm:3.0.0"
|
resolution: "slash@npm:3.0.0"
|
||||||
@ -7652,7 +7636,6 @@ __metadata:
|
|||||||
"@typescript-eslint/eslint-plugin": "npm:^7.9.0"
|
"@typescript-eslint/eslint-plugin": "npm:^7.9.0"
|
||||||
"@typescript-eslint/parser": "npm:^7.9.0"
|
"@typescript-eslint/parser": "npm:^7.9.0"
|
||||||
"@vitejs/plugin-react": "npm:^4.2.1"
|
"@vitejs/plugin-react": "npm:^4.2.1"
|
||||||
"@void-cat/api": "npm:^1.0.12"
|
|
||||||
"@webbtc/webln-types": "npm:^3.0.0"
|
"@webbtc/webln-types": "npm:^3.0.0"
|
||||||
"@webscopeio/react-textarea-autocomplete": "npm:^4.9.2"
|
"@webscopeio/react-textarea-autocomplete": "npm:^4.9.2"
|
||||||
autoprefixer: "npm:^10.4.19"
|
autoprefixer: "npm:^10.4.19"
|
||||||
|
Reference in New Issue
Block a user