Skip to content

Commit

Permalink
Add top zappers support page
Browse files Browse the repository at this point in the history
  • Loading branch information
hzrd149 committed Dec 1, 2024
1 parent 864ccc3 commit 694e261
Show file tree
Hide file tree
Showing 18 changed files with 490 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .changeset/tender-brooms-nail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostrudel": minor
---

Add top zappers support page
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"blossom-client-sdk": "next",
"blossom-drive-sdk": "^0.4.1",
"blurhash": "^2.0.5",
"canvas-confetti": "^1.9.3",
"chart.js": "^4.4.6",
"cheerio": "^1.0.0",
"chroma-js": "^2.6.0",
Expand Down Expand Up @@ -120,6 +121,7 @@
},
"devDependencies": {
"@changesets/cli": "^2.27.10",
"@types/canvas-confetti": "^1.6.4",
"@types/chroma-js": "^2.4.4",
"@types/debug": "^4.1.12",
"@types/dom-serial": "^1.0.6",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import AccountSettings from "./views/settings/accounts";
import ArticlesHomeView from "./views/articles";
import ArticleView from "./views/articles/article";
import WalletView from "./views/wallet";
import SupportView from "./views/support";
const TracksView = lazy(() => import("./views/tracks"));
const UserTracksTab = lazy(() => import("./views/user/tracks"));
const UserVideosTab = lazy(() => import("./views/user/videos"));
Expand Down Expand Up @@ -501,6 +502,10 @@ const router = createHashRouter([
path: "streams",
element: <StreamsView />,
},
{
path: "support",
children: [{ path: "", element: <SupportView /> }],
},
{
path: "tracks",
element: <TracksView />,
Expand Down
18 changes: 14 additions & 4 deletions src/components/event-zap-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@ import { eventStore, queryStore } from "../../services/event-store";
export type PayRequest = { invoice?: string; pubkey: string; error?: any };

// TODO: this is way to complicated, it needs to be broken into multiple parts / hooks
async function getPayRequestForPubkey(

/**
*
* @param pubkey pubkey to be zapped
* @param event event to be zapped
* @param amount amount in msats
* @param comment zap comment
* @param additionalRelays extra relays to set the zap to
* @returns
*/
export async function getPayRequestForPubkey(
pubkey: string,
event: NostrEvent | undefined,
amount: number,
Expand Down Expand Up @@ -65,12 +75,12 @@ async function getPayRequestForPubkey(

const mailboxes = eventStore.getReplaceable(kinds.RelayList, pubkey);
const userInbox = mailboxes ? getInboxes(mailboxes).slice(0, 4) : [];
const eventRelays = event ? getEventRelayHints(event, 4) : [];
const eventRelays = event ? getEventRelayHints(event, 2) : [];
const accountMailboxes = account ? eventStore.getReplaceable(kinds.RelayList, account?.pubkey) : undefined;
const outbox = relayScoreboardService
.getRankedRelays(accountMailboxes ? getOutboxes(accountMailboxes) : [])
.slice(0, 4);
const additional = relayScoreboardService.getRankedRelays(additionalRelays);
.slice(0, 2);
const additional = additionalRelays ? relayScoreboardService.getRankedRelays(additionalRelays) : [];

// create zap request
const zapRequest: DraftNostrEvent = {
Expand Down
20 changes: 17 additions & 3 deletions src/components/event-zap-modal/pay-step.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { useMount } from "react-use";
import { Alert, Button, ButtonGroup, Flex, IconButton, Spacer, useDisclosure, useToast } from "@chakra-ui/react";
import {
Alert,
Button,
ButtonGroup,
Flex,
FlexProps,
IconButton,
Spacer,
useDisclosure,
useToast,
} from "@chakra-ui/react";

import { PayRequest } from ".";
import UserAvatar from "../user/user-avatar";
Expand Down Expand Up @@ -77,7 +87,11 @@ function ErrorCard({ pubkey, error }: { pubkey: string; error: any }) {
);
}

export default function PayStep({ callbacks, onComplete }: { callbacks: PayRequest[]; onComplete: () => void }) {
export default function PayStep({
callbacks,
onComplete,
...props
}: Omit<FlexProps, "children"> & { callbacks: PayRequest[]; onComplete: () => void }) {
const [paid, setPaid] = useState<string[]>([]);
const { autoPayWithWebLN } = useAppSettings();

Expand Down Expand Up @@ -115,7 +129,7 @@ export default function PayStep({ callbacks, onComplete }: { callbacks: PayReque
});

return (
<Flex direction="column" gap="4">
<Flex direction="column" gap="4" {...props}>
{callbacks.map(({ pubkey, invoice, error }) => {
if (paid.includes(pubkey))
return (
Expand Down
4 changes: 2 additions & 2 deletions src/components/invoice-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ export function InvoiceModalContent({ invoice, onPaid }: CommonProps) {

return (
<Flex gap="2" direction="column">
{showQr.isOpen && <QrCodeSvg content={invoice} />}
{showQr.isOpen && <QrCodeSvg content={invoice} maxW="4in" mx="auto" />}
<Flex gap="2">
<Input value={invoice} readOnly />
<Input value={invoice} userSelect="all" onChange={() => {}} />
<IconButton
icon={<QrCodeIcon boxSize={6} />}
aria-label="Show QrCode"
Expand Down
42 changes: 11 additions & 31 deletions src/components/layout/nav-items.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useMemo } from "react";
import { Box, Button, ButtonProps, Icon, Link, Text, others, useDisclosure } from "@chakra-ui/react";
import { Box, Button, ButtonProps, Text } from "@chakra-ui/react";
import { Link as RouterLink, useLocation } from "react-router-dom";
import { nip19 } from "nostr-tools";
import dayjs from "dayjs";

import {
DirectMessagesIcon,
Expand All @@ -17,8 +16,6 @@ import {
} from "../icons";
import useCurrentAccount from "../../hooks/use-current-account";
import accountService from "../../services/account";
import { useLocalStorage } from "react-use";
import ZapModal from "../event-zap-modal";
import PuzzlePiece01 from "../icons/puzzle-piece-01";
import Package from "../icons/package";
import Rocket02 from "../icons/rocket-02";
Expand All @@ -27,15 +24,11 @@ import KeyboardShortcut from "../keyboard-shortcut";
import useRecentIds from "../../hooks/use-recent-ids";
import { internalApps, internalTools } from "../../views/other-stuff/apps";
import { App, AppIcon } from "../../views/other-stuff/component/app-card";
import Wallet02 from "../icons/wallet-02";

export default function NavItems() {
const location = useLocation();
const account = useCurrentAccount();

const donateModal = useDisclosure();
const [lastDonate, setLastDonate] = useLocalStorage<number>("last-donate");

const showShortcuts = useBreakpointValue({ base: false, md: true });

const buttonProps: ButtonProps = {
Expand Down Expand Up @@ -70,6 +63,7 @@ export default function NavItems() {
else if (location.pathname.startsWith("/torrents")) active = "tools";
else if (location.pathname.startsWith("/map")) active = "tools";
else if (location.pathname.startsWith("/profile")) active = "profile";
else if (location.pathname.startsWith("/support")) active = "support";
else if (location.pathname.startsWith("/other-stuff")) active = "other-stuff";
else if (
account &&
Expand Down Expand Up @@ -227,29 +221,15 @@ export default function NavItems() {
>
Settings
</Button>
{(lastDonate === undefined || dayjs.unix(lastDonate).isBefore(dayjs().subtract(1, "week"))) && (
<Button
as={Link}
leftIcon={<LightningIcon boxSize={6} color="yellow.400" />}
href="https://geyser.fund/project/nostrudel"
isExternal
onClick={(e) => {
e.preventDefault();
donateModal.onOpen();
}}
{...buttonProps}
>
Donate
</Button>
)}
{donateModal.isOpen && (
<ZapModal
isOpen
pubkey="713978c3094081b34fcf2f5491733b0c22728cd3b7a6946519d40f5f08598af8"
onClose={donateModal.onClose}
onZapped={() => setLastDonate(dayjs().unix())}
/>
)}
<Button
as={RouterLink}
to="/support"
leftIcon={<LightningIcon boxSize={6} color="yellow.400" />}
colorScheme={active === "support" ? "primary" : undefined}
{...buttonProps}
>
Support
</Button>
{account && (
<Button onClick={() => accountService.logout()} leftIcon={<LogoutIcon boxSize={6} />} {...buttonProps}>
Logout
Expand Down
2 changes: 0 additions & 2 deletions src/components/post-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,6 @@ export default function PostModal({
updateDraft(getValues());
}, [throttleValues]);

const imageUploadRef = useRef<HTMLInputElement | null>(null);

const textAreaRef = useRef<RefType | null>(null);
const insertText = useTextAreaInsertTextWithForm(textAreaRef, getValues, setValue);
const { onPaste } = useTextAreaUploadFile(insertText);
Expand Down
12 changes: 6 additions & 6 deletions src/components/post-modal/insert-image-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ export default function InsertImageButton({

return (
<>
<VisuallyHiddenInput
type="file"
accept="image/*,audio/*,video/*"
ref={imageUploadRef}
onChange={onFileInputChange}
/>
<IconButton
icon={<UploadImageIcon boxSize={6} />}
onClick={() => imageUploadRef.current?.click()}
isLoading={uploading}
{...props}
/>
<VisuallyHiddenInput
type="file"
accept="image/*,audio/*,video/*"
ref={imageUploadRef}
onChange={onFileInputChange}
/>
</>
);
}
10 changes: 7 additions & 3 deletions src/components/qr-code/qr-code-svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useMemo } from "react";

import { drawSvgPath } from "../../helpers/qrcode";
import { Ecc, QrCode } from "../../lib/qrcodegen";
import { Box, BoxProps } from "@chakra-ui/react";

export default function QrCodeSvg({
content,
lightColor = "white",
darkColor = "black",
border = 2,
}: {
...props
}: Omit<BoxProps, "children" | "border" | "content"> & {
content: string;
lightColor?: string;
darkColor?: string;
Expand All @@ -17,14 +19,16 @@ export default function QrCodeSvg({
const qrCode = useMemo(() => QrCode.encodeText(content, Ecc.LOW), [content]);

return (
<svg
<Box
as="svg"
{...props}
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox={`0 0 ${qrCode.size + border * 2} ${qrCode.size + border * 2}`}
stroke="none"
>
<rect width="100%" height="100%" fill={lightColor} />
<path d={drawSvgPath(qrCode, border)} fill={darkColor} />
</svg>
</Box>
);
}
2 changes: 2 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ export const NIP_89_CLIENT_TAG = [
"noStrudel",
"31990:266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5:1686066542546",
];

export const SUPPORT_PUBKEY = "713978c3094081b34fcf2f5491733b0c22728cd3b7a6946519d40f5f08598af8";
54 changes: 54 additions & 0 deletions src/views/support/components/not-quite-top-zap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Box, Card, CardBody, CardHeader, Flex, Grid, Spacer, Text, TextProps } from "@chakra-ui/react";
import { getZapPayment, getZapRequest, getZapSender } from "applesauce-core/helpers";
import { NostrEvent } from "nostr-tools";

import UserAvatar from "../../../components/user/user-avatar";
import UserLink from "../../../components/user/user-link";
import TextNoteContents from "../../../components/note/timeline-note/text-note-contents";
import { LightningIcon } from "../../../components/icons";
import Timestamp from "../../../components/timestamp";
import UserDnsIdentityIcon from "../../../components/user/user-dns-identity-icon";
import DebugEventButton from "../../../components/debug-modal/debug-event-button";
import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref";
import { TrustProvider } from "../../../providers/local/trust-provider";

export function NotQuiteTopZap({ zap, color }: { zap: NostrEvent; color: TextProps["color"] }) {
const sender = getZapSender(zap);
const request = getZapRequest(zap);
const payment = getZapPayment(zap);

const ref = useEventIntersectionRef(zap);

return (
<Card maxW="2xl" w="full" ref={ref} borderColor={color} variant="outline" mt="6">
<CardHeader display="flex" gap="2" px="4" py="2" alignItems="flex-start">
<UserAvatar pubkey={sender} size="lg" ml="-8" mt="-8" />
<Flex alignItems="center" gap="2">
<UserLink pubkey={sender} fontWeight="bold" fontSize="lg" />
<UserDnsIdentityIcon pubkey={sender} />
</Flex>
<Flex gap="2">
<LightningIcon color="yellow.400" boxSize={6} />
{payment?.amount && (
<Text color={color} fontSize="lg" fontWeight="bold">
{(payment.amount / 1000).toLocaleString()}
</Text>
)}
</Flex>

<Flex gap="2" ml="auto" alignItems="center">
<Timestamp timestamp={zap.created_at} />
<DebugEventButton event={zap} size="sm" variant="ghost" />
</Flex>
</CardHeader>

{request.content && (
<CardBody px="4" pt="0" pb="4">
<TrustProvider event={request}>
<TextNoteContents event={request} />
</TrustProvider>
</CardBody>
)}
</Card>
);
}
Loading

0 comments on commit 694e261

Please sign in to comment.