refactor: refresh

This commit is contained in:
2024-02-27 17:51:31 +00:00
parent 364d2c272f
commit f93a398039
75 changed files with 1434 additions and 2476 deletions

View File

@ -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>}
</>
);
}

View File

@ -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
View 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>
}

View File

@ -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} />
})
}

View File

@ -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>
);
}

View File

@ -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>

View File

@ -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>

View File

@ -61,7 +61,7 @@
.one-line:before,
.one-line:after {
background-color: #171717;
@apply bg-layer-2;
}
::-webkit-scrollbar {

View File

@ -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>

View File

@ -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>

View File

@ -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>
</>
)}
</>

View File

@ -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>
);