refactor: refresh
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { ChatZap, LiveChat } from "@/element/live-chat";
|
||||
import LiveVideoPlayer from "@/element/live-video-player";
|
||||
import { MuteButton } from "@/element/mute-button";
|
||||
@ -15,8 +14,9 @@ import { HTMLProps, ReactNode, useEffect, useMemo, useState } from "react";
|
||||
import { FormattedMessage, FormattedNumber } from "react-intl";
|
||||
import { Text } from "@/element/text";
|
||||
import { StreamTimer } from "@/element/stream-time";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import { DashboardRaidMenu } from "@/element/raid-menu";
|
||||
import { DefaultButton } from "@/element/buttons";
|
||||
import Modal from "@/element/modal";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const login = useLogin();
|
||||
@ -77,7 +77,7 @@ function DashboardForLink({ link }: { link: NostrLink }) {
|
||||
|
||||
function DashboardCard(props: HTMLProps<HTMLDivElement>) {
|
||||
return (
|
||||
<div {...props} className={classNames("px-4 py-6 rounded-3xl border border-gray-1", props.className)}>
|
||||
<div {...props} className={classNames("px-4 py-6 rounded-3xl border border-layer-1", props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
@ -91,8 +91,8 @@ function DashboardStatsCard({
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={classNames("flex-1 bg-gray-1 flex flex-col gap-1 px-4 py-2 rounded-xl", props.className)}>
|
||||
<div className="text-gray-3 font-medium">{name}</div>
|
||||
className={classNames("flex-1 bg-layer-1 flex flex-col gap-1 px-4 py-2 rounded-xl", props.className)}>
|
||||
<div className="text-layer-3 font-medium">{name}</div>
|
||||
<div>{value}</div>
|
||||
</div>
|
||||
);
|
||||
@ -106,13 +106,13 @@ function DashboardChatList({ link }: { link: NostrLink }) {
|
||||
}, [feed]);
|
||||
|
||||
return pubkeys.map(a => (
|
||||
<div className="flex justify-between items-center px-4 py-2 border-b border-gray-1">
|
||||
<div className="flex justify-between items-center px-4 py-2 border-b border-layer-1">
|
||||
<Profile pubkey={a} avatarSize={32} gap={4} />
|
||||
<div className="flex gap-2">
|
||||
<MuteButton pubkey={a} />
|
||||
<AsyncButton onClick={() => {}} className="font-bold">
|
||||
<DefaultButton onClick={() => { }} className="font-bold">
|
||||
<FormattedMessage defaultMessage="Zap" id="fBI91o" />
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</div>
|
||||
));
|
||||
@ -144,7 +144,7 @@ function DashboardZapColumn({ link }: { link: NostrLink }) {
|
||||
|
||||
function DashboardHighlightZap({ zap }: { zap: ParsedZap }) {
|
||||
return (
|
||||
<div className="px-4 py-6 bg-gray-1 flex flex-col gap-4 rounded-xl animate-flash">
|
||||
<div className="px-4 py-6 bg-layer-1 flex flex-col gap-4 rounded-xl animate-flash">
|
||||
<div className="flex justify-between items-center text-zap text-2xl font-semibold">
|
||||
<Profile
|
||||
pubkey={zap.sender ?? "anon"}
|
||||
@ -173,17 +173,13 @@ function DashboardHighlightZap({ zap }: { zap: ParsedZap }) {
|
||||
|
||||
function DashboardRaidButton({ link }: { link: NostrLink }) {
|
||||
const [show, setShow] = useState(false);
|
||||
return (
|
||||
<Dialog.Root open={show} onOpenChange={setShow}>
|
||||
<AsyncButton className="btn btn-primary" onClick={() => setShow(true)}>
|
||||
<FormattedMessage defaultMessage="Raid" id="4iBdw1" />
|
||||
</AsyncButton>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="dialog-overlay" />
|
||||
<Dialog.Content className="dialog-content">
|
||||
<DashboardRaidMenu link={link} onClose={() => setShow(false)} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
return (<>
|
||||
<DefaultButton onClick={() => setShow(true)}>
|
||||
<FormattedMessage defaultMessage="Raid" id="4iBdw1" />
|
||||
</DefaultButton>
|
||||
{show && <Modal id="raid-menu" onClose={() => setShow(false)}>
|
||||
<DashboardRaidMenu link={link} onClose={() => setShow(false)} />
|
||||
</Modal>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import "./layout.css";
|
||||
|
||||
import { CSSProperties, useEffect, useState, useSyncExternalStore } from "react";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
@ -17,8 +16,10 @@ import { Login } from "@/index";
|
||||
import { useLang } from "@/hooks/lang";
|
||||
import { AllLocales } from "@/intl";
|
||||
import { NewVersion } from "@/serviceWorker";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { trackEvent } from "@/utils";
|
||||
import { BorderButton, DefaultButton } from "@/element/buttons";
|
||||
import Modal from "@/element/modal";
|
||||
import Logo from "@/element/logo";
|
||||
|
||||
export function LayoutPage() {
|
||||
const navigate = useNavigate();
|
||||
@ -109,24 +110,15 @@ export function LayoutPage() {
|
||||
|
||||
function loggedOut() {
|
||||
if (login) return;
|
||||
|
||||
function handleLogin() {
|
||||
setShowLogin(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root open={showLogin} onOpenChange={setShowLogin}>
|
||||
<AsyncButton className="btn btn-border" onClick={handleLogin}>
|
||||
<FormattedMessage defaultMessage="Login" id="AyGauy" />
|
||||
<Icon name="login" />
|
||||
</AsyncButton>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay className="dialog-overlay" />
|
||||
<Dialog.Content className="dialog-content">
|
||||
<LoginSignup close={() => setShowLogin(false)} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
return (<>
|
||||
<BorderButton onClick={() => setShowLogin(true)}>
|
||||
<FormattedMessage defaultMessage="Login" id="AyGauy" />
|
||||
<Icon name="login" />
|
||||
</BorderButton>
|
||||
{showLogin && <Modal id="login">
|
||||
<LoginSignup close={() => setShowLogin(false)} />
|
||||
</Modal>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -141,16 +133,16 @@ export function LayoutPage() {
|
||||
</Helmet>
|
||||
<header>
|
||||
<div
|
||||
className="bg-white flex items-center pointer rounded-2xl aspect-square px-1"
|
||||
className="bg-white text-black flex items-center cursor-pointer rounded-2xl aspect-square px-1"
|
||||
onClick={() => navigate("/")}>
|
||||
<img src="/zap-stream.svg" width={40} />
|
||||
<Logo width={40} height={40} />
|
||||
</div>
|
||||
<div className="grow flex items-center gap-2"></div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
to="https://discord.gg/Wtg6NVDdbT"
|
||||
target="_blank"
|
||||
className="flex items-center max-md:hidden gap-1 bg-gray-1 hover:bg-gray-2 font-bold p-2 rounded-xl">
|
||||
className="flex items-center max-md:hidden gap-1 bg-layer-1 hover:bg-layer-2 font-bold p-2 rounded-xl">
|
||||
<Icon name="link" />
|
||||
Discord
|
||||
</Link>
|
||||
@ -182,9 +174,9 @@ function NewVersionBanner() {
|
||||
<FormattedMessage defaultMessage="Refresh the page to use the latest version" id="Gmiwnd" />
|
||||
</p>
|
||||
</div>
|
||||
<AsyncButton onClick={() => window.location.reload()} className="btn">
|
||||
<DefaultButton onClick={() => window.location.reload()} className="btn">
|
||||
<FormattedMessage defaultMessage="Refresh" id="rELDbB" />
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
25
src/pages/mock.tsx
Normal file
25
src/pages/mock.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { LIVE_STREAM } from "@/const";
|
||||
import { LiveChat } from "@/element/live-chat";
|
||||
import { SendZapsDialog } from "@/element/send-zap";
|
||||
import { EventBuilder, NostrLink } from "@snort/system";
|
||||
|
||||
export default function MockPage() {
|
||||
const pubkey = "cf45a6ba1363ad7ed213a078e710d24115ae721c9b47bd1ebf4458eaefb4c2a5";
|
||||
const fakeStream = new EventBuilder()
|
||||
.kind(LIVE_STREAM)
|
||||
.pubKey(pubkey)
|
||||
.tag(["d", "mock"])
|
||||
.tag(["title", "Example Stream"])
|
||||
.tag(["summary", "An example mock stream for debugging"])
|
||||
.tag(["streaming", "https://example.com/live.m3u8"])
|
||||
.tag(["t", "nostr"])
|
||||
.tag(["t", "mock"])
|
||||
.processContent()
|
||||
.build();
|
||||
const fakeStreamLink = NostrLink.fromEvent(fakeStream);
|
||||
|
||||
return <div className="">
|
||||
<LiveChat link={fakeStreamLink} ev={fakeStream} height={600} />
|
||||
<SendZapsDialog lnurl="donate@snort.social" aTag={fakeStreamLink.toEventTag()![1]} pubkey={pubkey} />
|
||||
</div>
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import "./profile-page.css";
|
||||
import { useMemo } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import * as Tabs from "@radix-ui/react-tabs";
|
||||
import { NostrPrefix, ParsedZap, TaggedNostrEvent, encodeTLV, parseNostrLink } from "@snort/system";
|
||||
import { CachedMetadata, NostrEvent, NostrLink, TaggedNostrEvent, parseNostrLink } from "@snort/system";
|
||||
import { useUserProfile } from "@snort/system-react";
|
||||
import { unwrap } from "@snort/shared";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
@ -13,128 +12,127 @@ import { VideoTile } from "@/element/video-tile";
|
||||
import { FollowButton } from "@/element/follow-button";
|
||||
import { MuteButton } from "@/element/mute-button";
|
||||
import { useProfile } from "@/hooks/profile";
|
||||
import useTopZappers from "@/hooks/top-zappers";
|
||||
import { Text } from "@/element/text";
|
||||
import { findTag } from "@/utils";
|
||||
import { StatePill } from "@/element/state-pill";
|
||||
import { Avatar } from "@/element/avatar";
|
||||
import { ZapperRow } from "@/element/zapper-row";
|
||||
import { StreamState } from "@/const";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
|
||||
function TopZappers({ zaps }: { zaps: ParsedZap[] }) {
|
||||
const zappers = useTopZappers(zaps);
|
||||
return (
|
||||
<section className="flex flex-col gap-2">
|
||||
{zappers.map(z => (
|
||||
<ZapperRow key={z.pubkey} pubkey={z.pubkey} total={z.total} />
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
import { DefaultButton } from "@/element/buttons";
|
||||
import { useGoals } from "@/hooks/goals";
|
||||
import { Goal } from "@/element/goal";
|
||||
import { TopZappers } from "@/element/top-zappers";
|
||||
import { useClips } from "@/hooks/clips";
|
||||
|
||||
const defaultBanner = "https://void.cat/d/Hn1AdN5UKmceuDkgDW847q.webp";
|
||||
|
||||
export function ProfilePage() {
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
const link = parseNostrLink(unwrap(params.npub));
|
||||
const profile = useUserProfile(link.id);
|
||||
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||
const { streams, zaps } = useProfile(link, true);
|
||||
const liveEvent = useMemo(() => {
|
||||
return streams.find(ev => findTag(ev, "status") === StreamState.Live);
|
||||
}, [streams]);
|
||||
const profile = useUserProfile(link.id);
|
||||
|
||||
const pastStreams = useMemo(() => {
|
||||
return streams.filter(ev => findTag(ev, "status") === StreamState.Ended);
|
||||
}, [streams]);
|
||||
const futureStreams = useMemo(() => {
|
||||
return streams.filter(ev => findTag(ev, "status") === StreamState.Planned);
|
||||
}, [streams]);
|
||||
const isLive = Boolean(liveEvent);
|
||||
|
||||
function goToLive() {
|
||||
if (liveEvent) {
|
||||
const d = findTag(liveEvent, "d") || "";
|
||||
const naddr = encodeTLV(NostrPrefix.Address, d, undefined, liveEvent.kind, liveEvent.pubkey);
|
||||
navigate(`/${naddr}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 max-sm:px-4">
|
||||
<div className="flex flex-col gap-3 px-4">
|
||||
<img
|
||||
className="rounded-xl object-cover h-[360px]"
|
||||
alt={profile?.name || link.id}
|
||||
src={profile?.banner ? profile?.banner : defaultBanner}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<Avatar user={profile} pubkey={link.id} size={88} className="border border-4" />
|
||||
{isLive && <StatePill state={StreamState.Live} onClick={goToLive} className="absolute bottom-0 -mb-2" />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{profile?.name && <h1 className="name">{profile.name}</h1>}
|
||||
{profile?.about && (
|
||||
<p className="text-neutral-400">
|
||||
<Text content={profile.about} tags={[]} />
|
||||
</p>
|
||||
)}
|
||||
<ProfileHeader link={link} profile={profile} streams={streams} />
|
||||
<div className="grid lg:grid-cols-2 gap-4 py-2">
|
||||
<div>
|
||||
<h3 className="text-xl py-2">
|
||||
<FormattedMessage defaultMessage="All Time Top Zappers" id="FIDK5Y" />
|
||||
</h3>
|
||||
<div className="flex flex-col gap-4">
|
||||
<TopZappers zaps={zaps} limit={10} avatarSize={40} showName={true} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
{zapTarget && (
|
||||
<SendZapsDialog
|
||||
aTag={liveEvent ? `${liveEvent.kind}:${liveEvent.pubkey}:${findTag(liveEvent, "d")}` : undefined}
|
||||
lnurl={zapTarget}
|
||||
button={
|
||||
<AsyncButton className="btn">
|
||||
<Icon name="zap-filled" className="zap-button-icon" />
|
||||
<FormattedMessage defaultMessage="Zap" id="fBI91o" />
|
||||
</AsyncButton>
|
||||
}
|
||||
targetName={profile?.name || link.id}
|
||||
/>
|
||||
)}
|
||||
<FollowButton pubkey={link.id} />
|
||||
<MuteButton pubkey={link.id} />
|
||||
<div>
|
||||
<h3 className="text-xl py-2">
|
||||
<FormattedMessage defaultMessage="Zap Goals" id="LEmxc8" />
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<ProfileZapGoals link={link} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs.Root className="tabs-root" defaultValue="top-zappers">
|
||||
<Tabs.List className="tabs-list" aria-label={`Information about ${profile ? profile.name : link.id}`}>
|
||||
<Tabs.Trigger className="tabs-tab" value="top-zappers">
|
||||
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||
<div className="tab-border"></div>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger className="tabs-tab" value="past-streams">
|
||||
<FormattedMessage defaultMessage="Past Streams" id="UfSot5" />
|
||||
<div className="tab-border"></div>
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger className="tabs-tab" value="schedule">
|
||||
<FormattedMessage defaultMessage="Schedule" id="hGQqkW" />
|
||||
<div className="tab-border"></div>
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
<Tabs.Content className="tabs-content" value="top-zappers">
|
||||
<TopZappers zaps={zaps} />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content className="tabs-content" value="past-streams">
|
||||
<ProfileStreamList streams={pastStreams} />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content className="tabs-content" value="schedule">
|
||||
<ProfileStreamList streams={futureStreams} />
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Recent Clips" id="XMGfiA" />
|
||||
</h1>
|
||||
<div className="flex gap-4">
|
||||
<ProfileClips link={link} />
|
||||
</div>
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Past Streams" id="UfSot5" />
|
||||
</h1>
|
||||
<ProfileStreamList streams={pastStreams} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileHeader({ profile, link, streams }: { profile?: CachedMetadata, link: NostrLink, streams: Array<NostrEvent> }) {
|
||||
const navigate = useNavigate();
|
||||
const liveEvent = useMemo(() => {
|
||||
return streams.find(ev => findTag(ev, "status") === StreamState.Live);
|
||||
}, [streams]);
|
||||
const zapTarget = profile?.lud16 ?? profile?.lud06;
|
||||
const isLive = Boolean(liveEvent);
|
||||
|
||||
function goToLive() {
|
||||
if (liveEvent) {
|
||||
const evLink = NostrLink.fromEvent(liveEvent);
|
||||
navigate(`/${evLink.encode()}`);
|
||||
}
|
||||
}
|
||||
|
||||
return <div className="flex max-sm:flex-col gap-3 justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative flex flex-col items-center">
|
||||
<Avatar user={profile} pubkey={link.id} size={88} className="border border-4" />
|
||||
{isLive && <StatePill state={StreamState.Live} onClick={goToLive} className="absolute bottom-0 -mb-2" />}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{profile?.name && <h1 className="name">{profile.name}</h1>}
|
||||
{profile?.about && (
|
||||
<p className="text-neutral-400">
|
||||
<Text content={profile.about} tags={[]} />
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-center">
|
||||
{zapTarget && (
|
||||
<SendZapsDialog
|
||||
aTag={liveEvent ? `${liveEvent.kind}:${liveEvent.pubkey}:${findTag(liveEvent, "d")}` : undefined}
|
||||
lnurl={zapTarget}
|
||||
button={
|
||||
<DefaultButton>
|
||||
<Icon name="zap-filled" className="zap-button-icon" />
|
||||
<FormattedMessage defaultMessage="Zap" id="fBI91o" />
|
||||
</DefaultButton>
|
||||
}
|
||||
targetName={profile?.name || link.id}
|
||||
/>
|
||||
)}
|
||||
<FollowButton pubkey={link.id} />
|
||||
<MuteButton pubkey={link.id} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
function ProfileStreamList({ streams }: { streams: Array<TaggedNostrEvent> }) {
|
||||
if (streams.length === 0) {
|
||||
return <FormattedMessage defaultMessage="No streams yet" id="0rVLjV" />
|
||||
}
|
||||
return (
|
||||
<div className="flex gap-3 flex-wrap justify-center">
|
||||
<div className="grid gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-8">
|
||||
{streams.map(ev => (
|
||||
<div key={ev.id} className="flex flex-col gap-1 sm:w-64 w-full">
|
||||
<div key={ev.id} className="flex flex-col gap-1">
|
||||
<VideoTile ev={ev} showAuthor={false} showStatus={false} />
|
||||
<span className="text-neutral-500">
|
||||
<FormattedMessage
|
||||
@ -150,3 +148,28 @@ function ProfileStreamList({ streams }: { streams: Array<TaggedNostrEvent> }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileZapGoals({ link }: { link: NostrLink }) {
|
||||
const limit = 5;
|
||||
const goals = useGoals(link.id, false, limit);
|
||||
if (goals.length === 0) {
|
||||
return <FormattedMessage defaultMessage="No goals yet" id="ZaNcK4" />
|
||||
}
|
||||
return goals
|
||||
.sort((a, b) => a.created_at > b.created_at ? -1 : 1)
|
||||
.slice(0, limit)
|
||||
.map(a => <div key={a.id} className="bg-layer-1 rounded-xl px-4 py-3">
|
||||
<Goal ev={a} confetti={false} />
|
||||
</div>);
|
||||
}
|
||||
|
||||
function ProfileClips({ link }: { link: NostrLink }) {
|
||||
const clips = useClips(link, 10);
|
||||
if (clips.length === 0) {
|
||||
return <FormattedMessage defaultMessage="No clips yet" id="ObZZEz" />
|
||||
}
|
||||
return clips.map(a => {
|
||||
const r = findTag(a, "r");
|
||||
return <video src={r} />
|
||||
})
|
||||
}
|
@ -6,7 +6,7 @@ import Owncast from "@/owncast.png";
|
||||
import Cloudflare from "@/cloudflare.png";
|
||||
import { ConfigureOwncast } from "./owncast";
|
||||
import { ConfigureNostrType } from "./nostr";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { DefaultButton } from "@/element/buttons";
|
||||
|
||||
export function StreamProvidersPage() {
|
||||
const navigate = useNavigate();
|
||||
@ -38,9 +38,9 @@ export function StreamProvidersPage() {
|
||||
<div className="paper">
|
||||
<h3>{mapName(p)}</h3>
|
||||
{mapLogo(p)}
|
||||
<AsyncButton className="btn btn-border" onClick={() => navigate(p)}>
|
||||
<DefaultButton onClick={() => navigate(p)}>
|
||||
+ Configure
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { StatePill } from "@/element/state-pill";
|
||||
import { StreamProviderInfo, StreamProviderStore } from "@/providers";
|
||||
import { NostrStreamProvider } from "@/providers/zsz";
|
||||
import { StreamState } from "@/const";
|
||||
import { DefaultButton } from "@/element/buttons";
|
||||
|
||||
export function ConfigureNostrType() {
|
||||
const [url, setUrl] = useState("");
|
||||
@ -55,14 +55,13 @@ export function ConfigureNostrType() {
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<AsyncButton
|
||||
className="btn btn-border"
|
||||
<DefaultButton
|
||||
onClick={() => {
|
||||
StreamProviderStore.add(new NostrStreamProvider(new URL(url).host, url));
|
||||
navigate("/");
|
||||
}}>
|
||||
<FormattedMessage defaultMessage="Save" id="jvo0vs" />
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -77,9 +76,9 @@ export function ConfigureNostrType() {
|
||||
<input type="text" placeholder="https://" value={url} onChange={e => setUrl(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<AsyncButton className="btn btn-primary" onClick={tryConnect}>
|
||||
<DefaultButton onClick={tryConnect}>
|
||||
<FormattedMessage defaultMessage="Connect" id="+vVZ/G" />
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
<div>{status()}</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { StatePill } from "@/element/state-pill";
|
||||
import { StreamProviderInfo, StreamProviderStore } from "@/providers";
|
||||
import { OwncastProvider } from "@/providers/owncast";
|
||||
import { StreamState } from "@/const";
|
||||
import { DefaultButton } from "@/element/buttons";
|
||||
|
||||
export function ConfigureOwncast() {
|
||||
const [url, setUrl] = useState("");
|
||||
@ -55,14 +55,13 @@ export function ConfigureOwncast() {
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<AsyncButton
|
||||
className="btn btn-border"
|
||||
<DefaultButton
|
||||
onClick={() => {
|
||||
StreamProviderStore.add(new OwncastProvider(url, token));
|
||||
navigate("/");
|
||||
}}>
|
||||
Save
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@ -83,9 +82,9 @@ export function ConfigureOwncast() {
|
||||
<input type="password" value={token} onChange={e => setToken(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<AsyncButton className="btn btn-primary" onClick={tryConnect}>
|
||||
<DefaultButton onClick={tryConnect}>
|
||||
Connect
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</div>
|
||||
<div>{status()}</div>
|
||||
</div>
|
||||
|
@ -61,7 +61,7 @@
|
||||
|
||||
.one-line:before,
|
||||
.one-line:after {
|
||||
background-color: #171717;
|
||||
@apply bg-layer-2;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
@ -11,11 +11,11 @@ import { Login } from "..";
|
||||
import { StatePill } from "@/element/state-pill";
|
||||
import { NostrStreamProvider } from "@/providers";
|
||||
import { StreamState } from "@/const";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { Layer1Button } from "@/element/buttons";
|
||||
|
||||
const enum Tab {
|
||||
Account,
|
||||
Notifications,
|
||||
Stream,
|
||||
}
|
||||
|
||||
export function SettingsPage() {
|
||||
@ -51,7 +51,9 @@ export function SettingsPage() {
|
||||
<p>
|
||||
<FormattedMessage defaultMessage="Private key" id="Bep/gA" />
|
||||
</p>
|
||||
<Copy text={hexToBech32("nsec", login.privateKey)} />
|
||||
<Layer1Button>
|
||||
<FormattedMessage defaultMessage="Copy" id="4l6vz1" />
|
||||
</Layer1Button>
|
||||
</div>
|
||||
)}
|
||||
<h1>
|
||||
@ -69,20 +71,24 @@ export function SettingsPage() {
|
||||
onClick={() => Login.setColor(a)}></div>
|
||||
))}
|
||||
</div>
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Stream Key" id="LknBsU" />
|
||||
</h1>
|
||||
<div className="flex flex-col gap-4">
|
||||
<NostrProviderDialog
|
||||
provider={unwrap(providers.find(a => a.name === "zap.stream")) as NostrStreamProvider}
|
||||
showEndpoints={true}
|
||||
showEditor={false}
|
||||
showForwards={true}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case Tab.Stream: {
|
||||
return <>
|
||||
<h1>
|
||||
<FormattedMessage defaultMessage="Stream" id="uYw2LD" />
|
||||
</h1>
|
||||
<div className="flex flex-col gap-4">
|
||||
<NostrProviderDialog
|
||||
provider={unwrap(providers.find(a => a.name === "zap.stream")) as NostrStreamProvider}
|
||||
showEndpoints={true}
|
||||
showEditor={false}
|
||||
showForwards={true}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +96,8 @@ export function SettingsPage() {
|
||||
switch (t) {
|
||||
case Tab.Account:
|
||||
return <FormattedMessage defaultMessage="Account" id="TwyMau" />;
|
||||
case Tab.Stream:
|
||||
return <FormattedMessage defaultMessage="Stream" id="uYw2LD" />;
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,13 +109,13 @@ export function SettingsPage() {
|
||||
</h1>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
{[Tab.Account].map(t => (
|
||||
<AsyncButton onClick={() => setTab(t)} className="rounded-xl px-3 py-2 bg-gray-2 hover:bg-gray-1">
|
||||
{[Tab.Account, Tab.Stream].map(t => (
|
||||
<Layer1Button onClick={() => setTab(t)} className={t === tab ? "active" : ""}>
|
||||
{tabName(t)}
|
||||
</AsyncButton>
|
||||
</Layer1Button>
|
||||
))}
|
||||
</div>
|
||||
<div className="p-5 bg-gray-2 rounded-3xl flex flex-col gap-3">{tabContent()}</div>
|
||||
<div className="p-5 bg-layer-1 rounded-3xl flex flex-col gap-3">{tabContent()}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,7 +11,6 @@ const LiveVideoPlayer = lazy(() => import("@/element/live-video-player"));
|
||||
import { extractStreamInfo, findTag, getEventFromLocationState, getHost } from "@/utils";
|
||||
import { Profile, getName } from "@/element/profile";
|
||||
import { LiveChat } from "@/element/live-chat";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { useLogin } from "@/hooks/login";
|
||||
import { useZapGoal } from "@/hooks/goals";
|
||||
import { SendZapsDialog } from "@/element/send-zap";
|
||||
@ -29,6 +28,8 @@ import { FollowButton } from "@/element/follow-button";
|
||||
import { ClipButton } from "@/element/clip-button";
|
||||
import { StreamState } from "@/const";
|
||||
import { NotificationsButton } from "@/element/notifications-button";
|
||||
import { WarningButton } from "@/element/buttons";
|
||||
import Pill from "@/element/pill";
|
||||
|
||||
function ProfileInfo({ ev, goal }: { ev?: TaggedNostrEvent; goal?: TaggedNostrEvent }) {
|
||||
const system = useContext(SnortContext);
|
||||
@ -60,22 +61,22 @@ function ProfileInfo({ ev, goal }: { ev?: TaggedNostrEvent; goal?: TaggedNostrEv
|
||||
<p>{summary}</p>
|
||||
<div className="tags">
|
||||
<StatePill state={status as StreamState} />
|
||||
<span className="pill bg-gray-1">
|
||||
<Pill>
|
||||
<FormattedMessage defaultMessage="{n} viewers" id="3adEeb" values={{ n: formatSats(viewers) }} />
|
||||
</span>
|
||||
</Pill>
|
||||
{status === StreamState.Live && (
|
||||
<span className="pill bg-gray-1">
|
||||
<Pill>
|
||||
<StreamTimer ev={ev} />
|
||||
</span>
|
||||
</Pill>
|
||||
)}
|
||||
{ev && <Tags ev={ev} />}
|
||||
</div>
|
||||
{isMine && (
|
||||
<div className="actions">
|
||||
{ev && <NewStreamDialog text="Edit" ev={ev} btnClassName="btn" />}
|
||||
<AsyncButton type="button" className="btn btn-warning" onClick={deleteStream}>
|
||||
<WarningButton onClick={deleteStream}>
|
||||
<FormattedMessage defaultMessage="Delete" id="K3r6DQ" />
|
||||
</AsyncButton>
|
||||
</WarningButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@ import { Views } from "./widgets/views";
|
||||
import { Music } from "./widgets/music";
|
||||
import groupBy from "lodash/groupBy";
|
||||
import { hexToBech32 } from "@snort/shared";
|
||||
import AsyncButton from "@/element/async-button";
|
||||
import { DefaultButton } from "@/element/buttons";
|
||||
|
||||
interface ZapAlertConfigurationProps {
|
||||
npub: string;
|
||||
@ -153,9 +153,9 @@ function ZapAlertConfiguration({ npub, baseUrl }: ZapAlertConfigurationProps) {
|
||||
onChange={ev => setTestText(ev.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<AsyncButton disabled={testText.length === 0} className="btn" onClick={testVoice}>
|
||||
<DefaultButton disabled={testText.length === 0} onClick={testVoice}>
|
||||
<FormattedMessage defaultMessage="Test voice" id="d5zWyh" />
|
||||
</AsyncButton>
|
||||
</DefaultButton>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@ -14,7 +14,7 @@ export function TopZappersWidget({ link }: { link: NostrLink }) {
|
||||
<FormattedMessage defaultMessage="Top Zappers" id="dVD/AR" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<TopZappers zaps={zaps} limit={3} />
|
||||
<TopZappers zaps={zaps} limit={3} className="border rounded-full px-2 py-1 border-layer-1 font-bold" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Reference in New Issue
Block a user