added zapping functionality on event cards and sidebar
This commit is contained in:
parent
b0017440a0
commit
c6ee922391
@ -1,7 +1,10 @@
|
|||||||
import { Search } from "./components/Search";
|
|
||||||
import AuthActions from "./components/AuthActions";
|
import AuthActions from "./components/AuthActions";
|
||||||
import Logo from "@/assets/Logo";
|
import Logo from "@/assets/Logo";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const Search = dynamic(() => import("./components/Search"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return (
|
return (
|
||||||
<header className="flex h-[var(--header-height)] shrink-0 grow-0 ">
|
<header className="flex h-[var(--header-height)] shrink-0 grow-0 ">
|
||||||
|
@ -43,6 +43,19 @@ type NavigationElement = {
|
|||||||
current: boolean;
|
current: boolean;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
} & (NavigationLink | NavigationButton);
|
} & (NavigationLink | NavigationButton);
|
||||||
|
const flockstrEvent = {
|
||||||
|
created_at: 1697736945,
|
||||||
|
content:
|
||||||
|
"Officially announcing Flockstr. Check it out at https://flockstr.com",
|
||||||
|
tags: [
|
||||||
|
["r", "https://flockstr.com"],
|
||||||
|
["client", "flockstr"],
|
||||||
|
],
|
||||||
|
kind: 1,
|
||||||
|
pubkey: "17717ad4d20e2a425cda0a2195624a0a4a73c4f6975f16b1593fc87fa46f2d58",
|
||||||
|
id: "a867ff28711eeab4767fb6bacbb33dfe17b2b5bbbff98f8e57f90a85ea684b0a",
|
||||||
|
sig: "37d8918e6da88d989467021a1f5809a3fbcab941ca1044d109ce261f29270d2d545aaa84297b7f224ae1ad7760263e50c317c24abc809034bcdb5c3260faf4b0",
|
||||||
|
};
|
||||||
|
|
||||||
export default function Sidebar() {
|
export default function Sidebar() {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
@ -76,7 +89,7 @@ export default function Sidebar() {
|
|||||||
active: false,
|
active: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onClick: () => modal?.show(<ZapPickerModal />),
|
onClick: () => modal?.show(<ZapPickerModal event={flockstrEvent} />),
|
||||||
name: "zap",
|
name: "zap",
|
||||||
label: "Zap Flockstr",
|
label: "Zap Flockstr",
|
||||||
icon: HiOutlineLightningBolt,
|
icon: HiOutlineLightningBolt,
|
||||||
|
70
app/(app)/_layout/components/CommandDialog.tsx
Normal file
70
app/(app)/_layout/components/CommandDialog.tsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useKeyboardShortcut } from "@/lib/hooks/useKeyboardShortcut";
|
||||||
|
import {
|
||||||
|
CalendarIcon,
|
||||||
|
EnvelopeClosedIcon,
|
||||||
|
FaceIcon,
|
||||||
|
GearIcon,
|
||||||
|
PersonIcon,
|
||||||
|
RocketIcon,
|
||||||
|
} from "@radix-ui/react-icons";
|
||||||
|
import {
|
||||||
|
CommandDialog,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
CommandSeparator,
|
||||||
|
CommandShortcut,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
import { atom, useAtom } from "jotai";
|
||||||
|
|
||||||
|
export const commandDialogAtom = atom(false);
|
||||||
|
|
||||||
|
export default function CommandDialogComponent() {
|
||||||
|
const [open, setOpen] = useAtom(commandDialogAtom);
|
||||||
|
useKeyboardShortcut(["ctrl", "k"], () => setOpen((open) => !open));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<CommandInput placeholder="Type a command or search..." />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
<CommandGroup heading="Suggestions">
|
||||||
|
<CommandItem>
|
||||||
|
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||||
|
<span>Calendar</span>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem>
|
||||||
|
<FaceIcon className="mr-2 h-4 w-4" />
|
||||||
|
<span>Search Emoji</span>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem>
|
||||||
|
<RocketIcon className="mr-2 h-4 w-4" />
|
||||||
|
<span>Launch</span>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup heading="Settings">
|
||||||
|
<CommandItem>
|
||||||
|
<PersonIcon className="mr-2 h-4 w-4" />
|
||||||
|
<span>Profile</span>
|
||||||
|
<CommandShortcut>⌘P</CommandShortcut>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem>
|
||||||
|
<EnvelopeClosedIcon className="mr-2 h-4 w-4" />
|
||||||
|
<span>Mail</span>
|
||||||
|
<CommandShortcut>⌘B</CommandShortcut>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem>
|
||||||
|
<GearIcon className="mr-2 h-4 w-4" />
|
||||||
|
<span>Settings</span>
|
||||||
|
<CommandShortcut>⌘S</CommandShortcut>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</CommandDialog>
|
||||||
|
);
|
||||||
|
}
|
@ -1,12 +1,17 @@
|
|||||||
import { Input } from "@/components/ui/input";
|
"use client";
|
||||||
|
|
||||||
export function Search() {
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { commandDialogAtom } from "./CommandDialog";
|
||||||
|
import { useAtom } from "jotai";
|
||||||
|
export default function Search() {
|
||||||
|
const [open, setOpen] = useAtom(commandDialogAtom);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
className="sm:w-[200px] lg:w-[300px]"
|
className="sm:w-[200px] lg:w-[300px]"
|
||||||
|
onFocus={() => setOpen(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,11 @@ import Header from "./Header";
|
|||||||
import Keystone from "./Keystone";
|
import Keystone from "./Keystone";
|
||||||
import MobileBanner from "./MobileBanner";
|
import MobileBanner from "./MobileBanner";
|
||||||
import Sidebar from "./Sidebar";
|
import Sidebar from "./Sidebar";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const CommandDialog = dynamic(() => import("./components/CommandDialog"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
export default function AppLayout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<main className="app-layout w-screen sm:absolute sm:inset-0">
|
<main className="app-layout w-screen sm:absolute sm:inset-0">
|
||||||
@ -23,6 +27,7 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
{/* BottomNav */}
|
{/* BottomNav */}
|
||||||
<BottomNav />
|
<BottomNav />
|
||||||
|
<CommandDialog />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,6 @@ export default function Kind1(props: Event) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
pubkey={pubkey}
|
|
||||||
createdAt={createdAt}
|
|
||||||
actionOptions={[
|
actionOptions={[
|
||||||
{
|
{
|
||||||
label: "View profile",
|
label: "View profile",
|
||||||
|
@ -19,9 +19,7 @@ export default function Kind30023(props: Event) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
pubkey={pubkey}
|
event={props}
|
||||||
contentTags={contentTags}
|
|
||||||
createdAt={createdAt}
|
|
||||||
actionOptions={[
|
actionOptions={[
|
||||||
{
|
{
|
||||||
label: "View profile",
|
label: "View profile",
|
||||||
|
@ -10,19 +10,16 @@ import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
|
|||||||
import ReactPlayer from "react-player";
|
import ReactPlayer from "react-player";
|
||||||
|
|
||||||
export default function Kind30311(props: Event) {
|
export default function Kind30311(props: Event) {
|
||||||
const { pubkey, created_at: createdAt, tags } = props;
|
const { pubkey, tags } = props;
|
||||||
const streamingUrl =
|
const streamingUrl =
|
||||||
getTagValues("streaming", tags) ?? getTagValues("recording", tags);
|
getTagValues("streaming", tags) ?? getTagValues("recording", tags);
|
||||||
const title = getTagValues("title", tags);
|
const title = getTagValues("title", tags);
|
||||||
const summary = getTagValues("summary", tags);
|
const summary = getTagValues("summary", tags);
|
||||||
const contentTags = getTagsValues("t", tags).filter(Boolean);
|
|
||||||
const npub = nip19.npubEncode(pubkey);
|
const npub = nip19.npubEncode(pubkey);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
pubkey={pubkey}
|
event={props}
|
||||||
createdAt={createdAt}
|
|
||||||
contentTags={contentTags}
|
|
||||||
actionOptions={[
|
actionOptions={[
|
||||||
{
|
{
|
||||||
label: "View profile",
|
label: "View profile",
|
||||||
|
@ -65,7 +65,7 @@ export default function Kind3745(props: Event) {
|
|||||||
return <KindCard {...decryptedEvent} />;
|
return <KindCard {...decryptedEvent} />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Container pubkey={pubkey}>
|
<Container event={props}>
|
||||||
<div className="relative ">
|
<div className="relative ">
|
||||||
<div className=" blur">
|
<div className=" blur">
|
||||||
<CardTitle className="mb-1.5 line-clamp-2 text-lg font-semibold">
|
<CardTitle className="mb-1.5 line-clamp-2 text-lg font-semibold">
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
HiOutlineHandThumbUp,
|
HiOutlineHandThumbUp,
|
||||||
HiOutlineChatBubbleLeftEllipsis,
|
HiOutlineChatBubbleLeftEllipsis,
|
||||||
} from "react-icons/hi2";
|
} from "react-icons/hi2";
|
||||||
import { HiOutlineLightningBolt } from "react-icons/hi";
|
import { HiOutlineLightningBolt } from "react-icons/hi";
|
||||||
|
import ZapPicker from "@/components/Modals/ZapPicker";
|
||||||
|
import { useModal } from "@/app/_providers/modal/provider";
|
||||||
|
import { NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { stopPropagation } from "@/lib/utils";
|
||||||
|
|
||||||
export default function Actions() {
|
type ActionProps = {
|
||||||
|
event: NostrEvent;
|
||||||
|
};
|
||||||
|
export default function Actions({ event }: ActionProps) {
|
||||||
|
const modal = useModal();
|
||||||
return (
|
return (
|
||||||
<div className="mt-3 flex items-center justify-between">
|
<div className="mt-3 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@ -17,7 +27,15 @@ export default function Actions() {
|
|||||||
<HiOutlineChatBubbleLeftEllipsis className="h-4 w-4" />
|
<HiOutlineChatBubbleLeftEllipsis className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button size={"sm"} className="gap-x-1.5">
|
<Button
|
||||||
|
onClick={(e) => {
|
||||||
|
stopPropagation(e);
|
||||||
|
console.log("captured");
|
||||||
|
modal?.show(<ZapPicker event={event} />);
|
||||||
|
}}
|
||||||
|
size={"sm"}
|
||||||
|
className="gap-x-1.5"
|
||||||
|
>
|
||||||
<HiOutlineLightningBolt className="h-4 w-4" />
|
<HiOutlineLightningBolt className="h-4 w-4" />
|
||||||
<span>zap</span>
|
<span>zap</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -7,10 +7,12 @@ import { formatDate } from "@/lib/utils/dates";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
import ProfileHeader, { LoadingProfileHeader } from "./ProfileHeader";
|
import ProfileHeader, { LoadingProfileHeader } from "./ProfileHeader";
|
||||||
|
import { getTagValues, getTagsValues } from "@/lib/nostr/utils";
|
||||||
import Actions from "./Actions";
|
import Actions from "./Actions";
|
||||||
import Tags from "./Tags";
|
import Tags from "./Tags";
|
||||||
import DropDownMenu from "@/components/DropDownMenu";
|
import DropDownMenu from "@/components/DropDownMenu";
|
||||||
|
import { NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
|
import { removeDuplicates } from "@/lib/utils";
|
||||||
type OptionLink = {
|
type OptionLink = {
|
||||||
href: string;
|
href: string;
|
||||||
type: "link";
|
type: "link";
|
||||||
@ -24,25 +26,54 @@ type Option = {
|
|||||||
} & (OptionLink | OptionButton);
|
} & (OptionLink | OptionButton);
|
||||||
|
|
||||||
type CreatorCardProps = {
|
type CreatorCardProps = {
|
||||||
pubkey?: string;
|
|
||||||
contentTags?: string[];
|
|
||||||
createdAt?: number;
|
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
actionOptions?: Option[];
|
actionOptions?: Option[];
|
||||||
|
event?: NostrEvent;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Container({
|
export default function Container({
|
||||||
children,
|
children,
|
||||||
createdAt,
|
|
||||||
contentTags,
|
|
||||||
pubkey,
|
|
||||||
actionOptions = [],
|
actionOptions = [],
|
||||||
|
event,
|
||||||
}: CreatorCardProps) {
|
}: CreatorCardProps) {
|
||||||
|
if (!event) {
|
||||||
return (
|
return (
|
||||||
<Card className="relative flex h-full w-full flex-col overflow-hidden @container">
|
<Card className="relative flex h-full w-full flex-col overflow-hidden @container">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-4">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-4">
|
||||||
{pubkey ? <ProfileHeader pubkey={pubkey} /> : <LoadingProfileHeader />}
|
<LoadingProfileHeader />
|
||||||
|
<div className="-mr-1 flex items-center gap-x-1.5 text-xs text-muted-foreground">
|
||||||
|
<DropDownMenu options={[]}>
|
||||||
|
<Button
|
||||||
|
size={"sm"}
|
||||||
|
variant={"ghost"}
|
||||||
|
className="center h-6 w-6 p-0"
|
||||||
|
>
|
||||||
|
<RiMoreFill className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropDownMenu>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex grow flex-col px-4 pb-3">
|
||||||
|
{children}
|
||||||
|
<div className="mt-auto"></div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { pubkey, tags, created_at: createdAt } = event;
|
||||||
|
const contentTags = removeDuplicates(getTagsValues("t", tags)).filter(
|
||||||
|
Boolean,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
onClick={() => {
|
||||||
|
console.log("CLICK IN CONTAINER");
|
||||||
|
}}
|
||||||
|
className="relative flex h-full w-full flex-col overflow-hidden @container"
|
||||||
|
>
|
||||||
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 p-4 pb-4">
|
||||||
|
{pubkey ? <ProfileHeader pubkey={pubkey} /> : <LoadingProfileHeader />}
|
||||||
<div className="-mr-1 flex items-center gap-x-1.5 text-xs text-muted-foreground">
|
<div className="-mr-1 flex items-center gap-x-1.5 text-xs text-muted-foreground">
|
||||||
{!!createdAt &&
|
{!!createdAt &&
|
||||||
formatDate(new Date(createdAt * 1000), "MMM Do, h:mm a")}
|
formatDate(new Date(createdAt * 1000), "MMM Do, h:mm a")}
|
||||||
@ -68,7 +99,7 @@ export default function Container({
|
|||||||
<div className="h-1.5" />
|
<div className="h-1.5" />
|
||||||
)}
|
)}
|
||||||
<div className="border-t">
|
<div className="border-t">
|
||||||
<Actions />
|
<Actions event={event} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
@ -16,8 +16,7 @@ export default function KindDefault(props: Event) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
pubkey={pubkey}
|
event={props}
|
||||||
createdAt={createdAt}
|
|
||||||
actionOptions={[
|
actionOptions={[
|
||||||
{
|
{
|
||||||
label: "View profile",
|
label: "View profile",
|
||||||
|
@ -3,23 +3,16 @@ import { useState, useRef, useEffect } from "react";
|
|||||||
import Template from "./Template";
|
import Template from "./Template";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useModal } from "@/app/_providers/modal/provider";
|
import { useModal } from "@/app/_providers/modal/provider";
|
||||||
import { nip19 } from "nostr-tools";
|
import { toast } from "sonner";
|
||||||
// import { useKeys } from "@/app/_providers/keysProvider";
|
|
||||||
import { useNDK } from "@/app/_providers/ndk";
|
import { useNDK } from "@/app/_providers/ndk";
|
||||||
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
import useCurrentUser from "@/lib/hooks/useCurrentUser";
|
||||||
import { HiOutlineLightningBolt } from "react-icons/hi";
|
import { HiOutlineLightningBolt } from "react-icons/hi";
|
||||||
import { RiSubtractFill, RiAddFill } from "react-icons/ri";
|
import { RiSubtractFill, RiAddFill } from "react-icons/ri";
|
||||||
import { formatCount } from "@/lib/utils";
|
import { formatCount } from "@/lib/utils";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
import { Label } from "@/components/ui/label";
|
||||||
Form,
|
import { type NostrEvent } from "@nostr-dev-kit/ndk";
|
||||||
FormControl,
|
import { sendZap } from "@/lib/actions/zap";
|
||||||
FormDescription,
|
|
||||||
FormField,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormMessage,
|
|
||||||
} from "@/components/ui/form";
|
|
||||||
|
|
||||||
const intervals = [
|
const intervals = [
|
||||||
10, 25, 50, 75, 100, 150, 200, 250, 350, 500, 750, 1000, 1250, 1500, 2_000,
|
10, 25, 50, 75, 100, 150, 200, 250, 350, 500, 750, 1000, 1250, 1500, 2_000,
|
||||||
@ -27,13 +20,18 @@ const intervals = [
|
|||||||
20_000, 25_000, 30_000, 40_000, 50_000, 75_000, 100_000, 150_000, 200_000,
|
20_000, 25_000, 30_000, 40_000, 50_000, 75_000, 100_000, 150_000, 200_000,
|
||||||
300_000, 500_000, 750_000, 1_000_000, 1_250_000, 1_500_000, 2_000_000,
|
300_000, 500_000, 750_000, 1_000_000, 1_250_000, 1_500_000, 2_000_000,
|
||||||
];
|
];
|
||||||
export default function ZapPicker() {
|
|
||||||
|
type ZapPickerProps = {
|
||||||
|
event: NostrEvent;
|
||||||
|
};
|
||||||
|
export default function ZapPicker({ event }: ZapPickerProps) {
|
||||||
const { loginWithNip07 } = useNDK();
|
const { loginWithNip07 } = useNDK();
|
||||||
const { loginWithPubkey, currentUser } = useCurrentUser();
|
const { loginWithPubkey, currentUser } = useCurrentUser();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [note, setNote] = useState("");
|
const [note, setNote] = useState("");
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const [sats, setSats] = useState(2000);
|
const [sats, setSats] = useState(2000);
|
||||||
|
const { ndk } = useNDK();
|
||||||
|
|
||||||
function onClick(type: "+" | "-") {
|
function onClick(type: "+" | "-") {
|
||||||
setSats((prev) => {
|
setSats((prev) => {
|
||||||
@ -48,6 +46,18 @@ export default function ZapPicker() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSendZap() {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const result = await sendZap(ndk!, sats, event, note);
|
||||||
|
toast.success("Zap Sent!");
|
||||||
|
} catch (err) {
|
||||||
|
console.log("error sending zap", err);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Template title="Send Zap!" className="md:max-w-[400px]">
|
<Template title="Send Zap!" className="md:max-w-[400px]">
|
||||||
<div className="flex flex-col gap-y-5">
|
<div className="flex flex-col gap-y-5">
|
||||||
@ -71,15 +81,7 @@ export default function ZapPicker() {
|
|||||||
Satoshis
|
Satoshis
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
|
||||||
<FormLabel>Note</FormLabel>
|
|
||||||
<Textarea
|
|
||||||
placeholder="Add a note..."
|
|
||||||
onChange={(e) => setNote(e.target.value)}
|
|
||||||
value={note}
|
|
||||||
className="auto-sizing"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
@ -91,9 +93,22 @@ export default function ZapPicker() {
|
|||||||
<span className="sr-only">Increase</span>
|
<span className="sr-only">Increase</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="pt-3">
|
||||||
|
<Label>Note</Label>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Add a note..."
|
||||||
|
onChange={(e) => setNote(e.target.value)}
|
||||||
|
value={note}
|
||||||
|
className="auto-sizing"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="w-full gap-x-1">
|
<Button
|
||||||
|
onClick={handleSendZap}
|
||||||
|
loading={isLoading}
|
||||||
|
className="w-full gap-x-1"
|
||||||
|
>
|
||||||
<span>Send Zap</span>
|
<span>Send Zap</span>
|
||||||
<HiOutlineLightningBolt className="h-4 w-4" />
|
<HiOutlineLightningBolt className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { DialogProps } from "@radix-ui/react-dialog"
|
import { DialogProps } from "@radix-ui/react-dialog";
|
||||||
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
|
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
|
||||||
import { Command as CommandPrimitive } from "cmdk"
|
import { Command as CommandPrimitive } from "cmdk";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
const Command = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive>,
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
@ -16,12 +16,12 @@ const Command = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
Command.displayName = CommandPrimitive.displayName
|
Command.displayName = CommandPrimitive.displayName;
|
||||||
|
|
||||||
interface CommandDialogProps extends DialogProps {}
|
interface CommandDialogProps extends DialogProps {}
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
|||||||
</Command>
|
</Command>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
const CommandInput = React.forwardRef<
|
const CommandInput = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
@ -47,14 +47,14 @@ const CommandInput = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))
|
));
|
||||||
|
|
||||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||||
|
|
||||||
const CommandList = React.forwardRef<
|
const CommandList = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive.List>,
|
React.ElementRef<typeof CommandPrimitive.List>,
|
||||||
@ -65,9 +65,9 @@ const CommandList = React.forwardRef<
|
|||||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
|
|
||||||
CommandList.displayName = CommandPrimitive.List.displayName
|
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||||
|
|
||||||
const CommandEmpty = React.forwardRef<
|
const CommandEmpty = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||||
@ -78,9 +78,9 @@ const CommandEmpty = React.forwardRef<
|
|||||||
className="py-6 text-center text-sm"
|
className="py-6 text-center text-sm"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
|
|
||||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||||
|
|
||||||
const CommandGroup = React.forwardRef<
|
const CommandGroup = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||||
@ -90,13 +90,13 @@ const CommandGroup = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
|
|
||||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||||
|
|
||||||
const CommandSeparator = React.forwardRef<
|
const CommandSeparator = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
@ -107,8 +107,8 @@ const CommandSeparator = React.forwardRef<
|
|||||||
className={cn("-mx-1 h-px bg-border", className)}
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||||
|
|
||||||
const CommandItem = React.forwardRef<
|
const CommandItem = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||||
@ -118,13 +118,13 @@ const CommandItem = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
|
|
||||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||||
|
|
||||||
const CommandShortcut = ({
|
const CommandShortcut = ({
|
||||||
className,
|
className,
|
||||||
@ -134,13 +134,13 @@ const CommandShortcut = ({
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
CommandShortcut.displayName = "CommandShortcut"
|
CommandShortcut.displayName = "CommandShortcut";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Command,
|
Command,
|
||||||
@ -152,4 +152,4 @@ export {
|
|||||||
CommandItem,
|
CommandItem,
|
||||||
CommandShortcut,
|
CommandShortcut,
|
||||||
CommandSeparator,
|
CommandSeparator,
|
||||||
}
|
};
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
import { Cross2Icon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Dialog = DialogPrimitive.Root
|
const Dialog = DialogPrimitive.Root;
|
||||||
|
|
||||||
const DialogTrigger = DialogPrimitive.Trigger
|
const DialogTrigger = DialogPrimitive.Trigger;
|
||||||
|
|
||||||
const DialogPortal = DialogPrimitive.Portal
|
const DialogPortal = DialogPrimitive.Portal;
|
||||||
|
|
||||||
const DialogOverlay = React.forwardRef<
|
const DialogOverlay = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
@ -19,13 +19,13 @@ const DialogOverlay = React.forwardRef<
|
|||||||
<DialogPrimitive.Overlay
|
<DialogPrimitive.Overlay
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
"z-overlay- fixed inset-0 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
const DialogContent = React.forwardRef<
|
const DialogContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
@ -36,20 +36,20 @@ const DialogContent = React.forwardRef<
|
|||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
"z-overlay fixed left-[50%] top-[50%] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none">
|
||||||
<Cross2Icon className="h-4 w-4" />
|
<Cross2Icon className="h-4 w-4" />
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
</DialogPrimitive.Close>
|
</DialogPrimitive.Close>
|
||||||
</DialogPrimitive.Content>
|
</DialogPrimitive.Content>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
))
|
));
|
||||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||||
|
|
||||||
const DialogHeader = ({
|
const DialogHeader = ({
|
||||||
className,
|
className,
|
||||||
@ -58,12 +58,12 @@ const DialogHeader = ({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
DialogHeader.displayName = "DialogHeader"
|
DialogHeader.displayName = "DialogHeader";
|
||||||
|
|
||||||
const DialogFooter = ({
|
const DialogFooter = ({
|
||||||
className,
|
className,
|
||||||
@ -72,12 +72,12 @@ const DialogFooter = ({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
DialogFooter.displayName = "DialogFooter"
|
DialogFooter.displayName = "DialogFooter";
|
||||||
|
|
||||||
const DialogTitle = React.forwardRef<
|
const DialogTitle = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
@ -87,12 +87,12 @@ const DialogTitle = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-lg font-semibold leading-none tracking-tight",
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
||||||
|
|
||||||
const DialogDescription = React.forwardRef<
|
const DialogDescription = React.forwardRef<
|
||||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
@ -103,8 +103,8 @@ const DialogDescription = React.forwardRef<
|
|||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -116,4 +116,4 @@ export {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogDescription,
|
DialogDescription,
|
||||||
}
|
};
|
||||||
|
@ -28,7 +28,7 @@ export async function sendZap(
|
|||||||
) {
|
) {
|
||||||
log("func", "sendZap");
|
log("func", "sendZap");
|
||||||
const event = await new NDKEvent(ndk, _event);
|
const event = await new NDKEvent(ndk, _event);
|
||||||
log("info", event.toString());
|
log("info", JSON.stringify(event));
|
||||||
const pr = await event.zap(amount * 1000, comment);
|
const pr = await event.zap(amount * 1000, comment);
|
||||||
if (!pr) {
|
if (!pr) {
|
||||||
log("info", "No PR");
|
log("info", "No PR");
|
||||||
|
@ -88,7 +88,7 @@ export function log(
|
|||||||
type: "info" | "error" | "warn" | "func",
|
type: "info" | "error" | "warn" | "func",
|
||||||
...args: unknown[]
|
...args: unknown[]
|
||||||
) {
|
) {
|
||||||
const isOn = true;
|
const isOn = process.env.NODE_ENV === "development";
|
||||||
if (!isOn) return;
|
if (!isOn) return;
|
||||||
const consoleType = type === "func" ? "info" : type;
|
const consoleType = type === "func" ? "info" : type;
|
||||||
const items = [...args].map((a) => `%c${a}`);
|
const items = [...args].map((a) => `%c${a}`);
|
||||||
@ -115,3 +115,8 @@ export function btcToSats(amount: number) {
|
|||||||
export function satsToBtc(amount: number) {
|
export function satsToBtc(amount: number) {
|
||||||
return amount / 100_000_000;
|
return amount / 100_000_000;
|
||||||
}
|
}
|
||||||
|
export function stopPropagation(e: React.MouseEvent) {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"focus-trap-react": "^10.2.3",
|
"focus-trap-react": "^10.2.3",
|
||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
|
"jotai": "^2.4.3",
|
||||||
"next": "13.5.4",
|
"next": "13.5.4",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"node-html-parser": "^6.1.10",
|
"node-html-parser": "^6.1.10",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user