feat: Added likes and experimental zaps
This commit is contained in:
parent
fad5a94597
commit
0a3572afe0
19
package-lock.json
generated
19
package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"name": "nostr-slideshow",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@nostr-dev-kit/ndk": "^0.8.1",
|
||||
"@nostr-dev-kit/ndk": "^0.8.3",
|
||||
"@nostr-dev-kit/ndk-react": "^0.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^1.14.0",
|
||||
@ -27,6 +27,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"@webbtc/webln-types": "^1.0.13",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
@ -969,9 +970,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@nostr-dev-kit/ndk": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.8.1.tgz",
|
||||
"integrity": "sha512-uI41sCs+7CxtKGIKXQGZjdwvksfeCwd83bB2yrJCePx4oIkEMMH1gVRYsNfQIMFBPejnU2bfBqcO2zEP9RzIFg==",
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-0.8.3.tgz",
|
||||
"integrity": "sha512-njdcTN0+/TW0xOqd+eQco1HN735f2uxv1wXbFUxKlkxd9ApnAlondqMZB69byHMVJuwA/iNbMZKdiPrAEGGJ3w==",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^1.3.1",
|
||||
"@noble/secp256k1": "^2.0.0",
|
||||
@ -1564,6 +1565,16 @@
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@webbtc/webln-types": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@webbtc/webln-types/-/webln-types-1.0.13.tgz",
|
||||
"integrity": "sha512-SBhqy1scA9xYUBq9GqwFFq0YpTDRUt1AHM0a8f+nJtMLNghouYSJrjj83Ax2l0btGHng8pRt8gytga6k5VaMFw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "lightning",
|
||||
"url": "hello@getalby.com"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"analyze": "vite-bundle-visualizer"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nostr-dev-kit/ndk": "^0.8.1",
|
||||
"@nostr-dev-kit/ndk": "^0.8.3",
|
||||
"@nostr-dev-kit/ndk-react": "^0.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"nostr-tools": "^1.14.0",
|
||||
@ -31,6 +31,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^6.2.1",
|
||||
"@typescript-eslint/parser": "^6.2.1",
|
||||
"@vitejs/plugin-react": "^4.0.4",
|
||||
"@webbtc/webln-types": "^1.0.13",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
|
13
src/App.tsx
13
src/App.tsx
@ -5,10 +5,15 @@ import useDisclaimerState from './utils/useDisclaimerState';
|
||||
import useNav from './utils/useNav';
|
||||
import { useEffect } from 'react';
|
||||
import { defaultHashTags } from './components/env';
|
||||
import { useNDK } from '@nostr-dev-kit/ndk-react';
|
||||
import { useGlobalState } from './utils/globalState';
|
||||
|
||||
const App = () => {
|
||||
const { disclaimerAccepted, setDisclaimerAccepted } = useDisclaimerState();
|
||||
const { nav, currentSettings } = useNav();
|
||||
const { loginWithNip07, ndk } = useNDK();
|
||||
|
||||
const [state, setState] = useGlobalState();
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSettings.npubs.length == 0 && currentSettings.tags.length == 0) {
|
||||
@ -16,8 +21,16 @@ const App = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onLogin = async () => {
|
||||
const result = await loginWithNip07();
|
||||
console.log(result);
|
||||
result && setState({ userNPub: result.npub });
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{JSON.stringify(ndk?.signer)}
|
||||
{JSON.stringify(state)}
|
||||
<button onClick={onLogin}>Login</button>
|
||||
{disclaimerAccepted ? (
|
||||
<SlideShow />
|
||||
) : (
|
||||
|
@ -1,3 +1,30 @@
|
||||
|
||||
@keyframes bump {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
50% {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.details {
|
||||
/*
|
||||
position: fixed;
|
||||
@ -56,6 +83,34 @@
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.details-contents .heart svg {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
cursor: pointer;
|
||||
fill: #ff0000;
|
||||
}
|
||||
|
||||
.details-contents .zap svg {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
cursor: pointer;
|
||||
fill: rgb(228, 185, 104);
|
||||
}
|
||||
|
||||
.details-contents .zap.zapped svg {
|
||||
fill: orange;
|
||||
animation: bump 1s ease-in-out
|
||||
}
|
||||
|
||||
.details-contents .zap.error {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
.details-contents .zap.zapping svg {
|
||||
animation: rotate 2s ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.details-contents .detail-image {
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
|
@ -2,10 +2,15 @@ import { NostrImage } from '../nostrImageDownload';
|
||||
import './DetailsView.css';
|
||||
import { useNDK } from '@nostr-dev-kit/ndk-react';
|
||||
import DetailsAuthor from './DetailsAuthor';
|
||||
import { useMemo } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import uniq from 'lodash/uniq';
|
||||
import useNav from '../../utils/useNav';
|
||||
import CloseButton from '../CloseButton/CloseButton';
|
||||
import IconHeart from '../Icons/IconHeart';
|
||||
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk';
|
||||
import { Kind, nip19 } from 'nostr-tools';
|
||||
import { useGlobalState } from '../../utils/globalState';
|
||||
import IconBolt from '../Icons/IconBolt';
|
||||
|
||||
type DetailsViewProps = {
|
||||
images: NostrImage[];
|
||||
@ -13,8 +18,14 @@ type DetailsViewProps = {
|
||||
setActiveImageIdx: (idx: number | undefined) => void;
|
||||
};
|
||||
|
||||
type ZapState = 'none' | 'zapped' | 'zapping' | 'error';
|
||||
|
||||
const DetailsView = ({ images, activeImageIdx, setActiveImageIdx }: DetailsViewProps) => {
|
||||
const { getProfile } = useNDK();
|
||||
const { getProfile, ndk } = useNDK();
|
||||
const [selfLiked, setSelfLiked] = useState(false);
|
||||
const [zapState, setZapState] = useState<ZapState>('none');
|
||||
|
||||
const [state, setState] = useGlobalState();
|
||||
const currentImage = useMemo(
|
||||
() => (activeImageIdx !== undefined ? images[activeImageIdx] : undefined),
|
||||
[images, activeImageIdx]
|
||||
@ -22,6 +33,78 @@ const DetailsView = ({ images, activeImageIdx, setActiveImageIdx }: DetailsViewP
|
||||
const activeProfile = currentImage?.author !== undefined ? getProfile(currentImage?.author) : undefined;
|
||||
const { nav, currentSettings } = useNav();
|
||||
|
||||
useEffect(() => {
|
||||
setSelfLiked(false);
|
||||
setZapState("none");
|
||||
|
||||
if (!currentImage?.noteId || !state.userNPub) return;
|
||||
|
||||
const filter: NDKFilter = { kinds: [Kind.Reaction], '#e': [currentImage?.noteId] };
|
||||
|
||||
filter.authors = [nip19.decode(state.userNPub).data as string];
|
||||
|
||||
currentImage?.noteId &&
|
||||
ndk?.fetchEvents(filter).then(events => {
|
||||
setSelfLiked(events.size > 0);
|
||||
});
|
||||
}, [currentImage?.event.id]);
|
||||
|
||||
const heartClick = async (currentImage: NostrImage) => {
|
||||
console.log('heartClick');
|
||||
if (!state.userNPub) return;
|
||||
|
||||
const ev = new NDKEvent(ndk, {
|
||||
kind: Kind.Reaction,
|
||||
pubkey: nip19.decode(state.userNPub).data as string,
|
||||
created_at: Math.floor(new Date().getTime() / 1000),
|
||||
content: '+',
|
||||
tags: [
|
||||
['e', currentImage.noteId],
|
||||
['p', currentImage.authorId],
|
||||
],
|
||||
});
|
||||
console.log(ev);
|
||||
await ev.publish();
|
||||
setSelfLiked(true);
|
||||
};
|
||||
|
||||
const zapClick = async (currentImage: NostrImage) => {
|
||||
setZapState('zapping');
|
||||
console.log('zapClick');
|
||||
if (!state.userNPub) return;
|
||||
|
||||
if (!window.webln) {
|
||||
console.error('No webln found');
|
||||
setZapState('error');
|
||||
return;
|
||||
}
|
||||
console.log('zapClick2');
|
||||
|
||||
const ev = await ndk?.fetchEvent(currentImage.noteId);
|
||||
|
||||
if (!ev) {
|
||||
console.error('No event found for noteId: ' + currentImage.noteId);
|
||||
setZapState('error');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(ev);
|
||||
const invoice = await ev.zap(21000, 'Nice!');
|
||||
console.log('zapClick3');
|
||||
|
||||
console.log(invoice);
|
||||
if (!invoice) {
|
||||
console.error('No invoice found');
|
||||
setZapState('error');
|
||||
return;
|
||||
}
|
||||
await window.webln.enable();
|
||||
await window.webln.sendPayment(invoice);
|
||||
|
||||
setZapState('zapped');
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="details">
|
||||
<CloseButton onClick={() => setActiveImageIdx(undefined)}></CloseButton>
|
||||
@ -35,6 +118,16 @@ const DetailsView = ({ images, activeImageIdx, setActiveImageIdx }: DetailsViewP
|
||||
></DetailsAuthor>
|
||||
|
||||
<div>{currentImage?.content}</div>
|
||||
{state.userNPub && (
|
||||
<>
|
||||
<div className="heart" onClick={() => currentImage && heartClick(currentImage)}>
|
||||
<IconHeart filled={selfLiked}></IconHeart>
|
||||
</div>
|
||||
<div className={`zap ${zapState}`} onClick={() => currentImage && zapClick(currentImage)}>
|
||||
<IconBolt></IconBolt>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
{uniq(currentImage?.tags).map(t => (
|
||||
<>
|
||||
|
7
src/components/Icons/IconBolt.tsx
Normal file
7
src/components/Icons/IconBolt.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
const IconBolt = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512">
|
||||
<path d="M349.4 44.6c5.9-13.7 1.5-29.7-10.6-38.5s-28.6-8-39.9 1.8l-256 224c-10 8.8-13.6 22.9-8.9 35.3S50.7 288 64 288H175.5L98.6 467.4c-5.9 13.7-1.5 29.7 10.6 38.5s28.6 8 39.9-1.8l256-224c10-8.8 13.6-22.9 8.9-35.3s-16.6-20.7-30-20.7H272.5L349.4 44.6z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default IconBolt;
|
16
src/components/Icons/IconHeart.tsx
Normal file
16
src/components/Icons/IconHeart.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
const IconHeart = ({ filled }: { filled: boolean }) => {
|
||||
if (filled)
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512">
|
||||
<path d="M47.6 300.4L228.3 469.1c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9L464.4 300.4c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141C347 36.5 300.6 51.4 268 84L256 96 244 84c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z" />
|
||||
</svg>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512">
|
||||
<path d="M225.8 468.2l-2.5-2.3L48.1 303.2C17.4 274.7 0 234.7 0 192.8v-3.3c0-70.4 50-130.8 119.2-144C158.6 37.9 198.9 47 231 69.6c9 6.4 17.4 13.8 25 22.3c4.2-4.8 8.7-9.2 13.5-13.3c3.7-3.2 7.5-6.2 11.5-9c0 0 0 0 0 0C313.1 47 353.4 37.9 392.8 45.4C462 58.6 512 119.1 512 189.5v3.3c0 41.9-17.4 81.9-48.1 110.4L288.7 465.9l-2.5 2.3c-8.2 7.6-19 11.9-30.2 11.9s-22-4.2-30.2-11.9zM239.1 145c-.4-.3-.7-.7-1-1.1l-17.8-20c0 0-.1-.1-.1-.1c0 0 0 0 0 0c-23.1-25.9-58-37.7-92-31.2C81.6 101.5 48 142.1 48 189.5v3.3c0 28.5 11.9 55.8 32.8 75.2L256 430.7 431.2 268c20.9-19.4 32.8-46.7 32.8-75.2v-3.3c0-47.3-33.6-88-80.1-96.9c-34-6.5-69 5.4-92 31.2c0 0 0 0-.1 .1s0 0-.1 .1l-17.8 20c-.3 .4-.7 .7-1 1.1c-4.5 4.5-10.6 7-16.9 7s-12.4-2.5-16.9-7z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconHeart;
|
@ -2,7 +2,6 @@ import { useNDK } from '@nostr-dev-kit/ndk-react';
|
||||
import './SlideShow.css';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
NostrEvent,
|
||||
NostrImage,
|
||||
buildFilter,
|
||||
extractImageUrls,
|
||||
@ -24,6 +23,7 @@ import IconSettings from './Icons/IconSettings';
|
||||
import IconPlay from './Icons/IconPlay';
|
||||
import IconGrid from './Icons/IconGrid';
|
||||
import useNav from '../utils/useNav';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
/*
|
||||
FEATURES:
|
||||
@ -53,7 +53,7 @@ FEATURES:
|
||||
|
||||
const SlideShow = () => {
|
||||
const { ndk } = useNDK();
|
||||
const [posts, setPosts] = useState<NostrEvent[]>([]);
|
||||
const [posts, setPosts] = useState<NDKEvent[]>([]);
|
||||
const images = useRef<NostrImage[]>([]);
|
||||
const fetchTimeoutHandle = useRef(0);
|
||||
const [showGrid, setShowGrid] = useState(false);
|
||||
@ -68,9 +68,9 @@ const SlideShow = () => {
|
||||
|
||||
const postSubscription = ndk.subscribe(buildFilter(settings.tags, settings.npubs, settings.showReposts));
|
||||
|
||||
postSubscription.on('event', (event: NostrEvent) => {
|
||||
postSubscription.on('event', (event: NDKEvent) => {
|
||||
setPosts(oldPosts => {
|
||||
event.isReply = isReply(event);
|
||||
//event.isReply = isReply(event);
|
||||
|
||||
if (event.kind === 1063) {
|
||||
const urlTag = event?.tags?.find(t => t[0]=='url')
|
||||
@ -84,7 +84,7 @@ const SlideShow = () => {
|
||||
const repostedEvent = JSON.parse(event.content);
|
||||
if (repostedEvent) {
|
||||
event = repostedEvent;
|
||||
event.isRepost = true;
|
||||
//event.isRepost = true;
|
||||
}
|
||||
} catch (e) {
|
||||
// ingore, the content is no valid json
|
||||
@ -93,7 +93,7 @@ const SlideShow = () => {
|
||||
|
||||
if (
|
||||
!blockedPublicKeys.includes(event.pubkey.toLowerCase()) && // remove blocked authors
|
||||
(settings.showReplies || !event.isReply) &&
|
||||
(settings.showReplies || !isReply(event)) &&
|
||||
oldPosts.findIndex(p => p.id === event.id) === -1 && // not duplicate
|
||||
(settings.showAdult || !isAdultRelated(event))
|
||||
) {
|
||||
@ -128,12 +128,14 @@ const SlideShow = () => {
|
||||
return extractImageUrls(p.content)
|
||||
.filter(url => isImage(url) || isVideo(url))
|
||||
.map(url => ({
|
||||
event: p,
|
||||
url,
|
||||
author: nip19.npubEncode(p.pubkey),
|
||||
authorId: p.pubkey,
|
||||
content: prepareContent(p.content),
|
||||
type: isVideo(url) ? 'video' : 'image',
|
||||
timestamp: p.created_at,
|
||||
noteId: p.id ? nip19.noteEncode(p.id) : '',
|
||||
noteId: p.id || '',
|
||||
tags: p.tags?.filter((t: string[]) => t[0] === 't').map((t: string[]) => t[1].toLowerCase()) || [],
|
||||
}));
|
||||
}),
|
||||
|
@ -146,6 +146,7 @@ export const adultNPubs = [
|
||||
'npub1z0xv9t5w6evrcg860kmgqq5tfj55mz84ta40uszjnfp9uhw2clkq63yrak', // ???
|
||||
'npub1hpxzg0p4hrmfvqmrusa4lkyx0ay53k2gwkjr50qe2cedj3vkufhs030ff0', // Spankingbot
|
||||
'npub1p4j4zfxvdgjrs26wx5dh9uvsvqfv8xa7ew89vv60nxang8cn0sxshyj28r', // Porn search bot
|
||||
'npub1tsrs6ptjnq5hluxawfme5sfxalfscapequm3ej0yfw65scwu8lys8q7y7l', // 💜 🔞EUPHORIA 🔞💜
|
||||
];
|
||||
|
||||
export const adultPublicKeys = adultNPubs.map(npub => (nip19.decode(npub).data as string).toLowerCase());
|
||||
@ -171,6 +172,5 @@ export const defaultRelays = [
|
||||
'wss://nostr.wine',
|
||||
// "wss://nostr1.current.fyi/",
|
||||
'wss://purplepag.es/', // needed for user profiles
|
||||
'wss://n-word.sharivegas.com/', // needed for mostr.pub profiles
|
||||
//"wss://feeds.nostr.band/pics",
|
||||
];
|
||||
|
@ -1,28 +1,20 @@
|
||||
import { NDKFilter, NDKKind, NDKTag } from '@nostr-dev-kit/ndk';
|
||||
import { NDKEvent, NDKFilter, NDKTag } from '@nostr-dev-kit/ndk';
|
||||
import { Kind, nip19 } from 'nostr-tools';
|
||||
import { adultContentTags, adultPublicKeys } from './env';
|
||||
|
||||
export type NostrImage = {
|
||||
url: string;
|
||||
author: string;
|
||||
authorId: string; // PubKey
|
||||
tags: string[];
|
||||
content?: string;
|
||||
timestamp?: number;
|
||||
noteId: string;
|
||||
type: 'image' | 'video';
|
||||
event: NDKEvent;
|
||||
};
|
||||
|
||||
export interface NostrEvent {
|
||||
created_at: number;
|
||||
content: string;
|
||||
tags?: NDKTag[];
|
||||
kind?: NDKKind | number;
|
||||
pubkey: string;
|
||||
id?: string;
|
||||
sig?: string;
|
||||
isRepost: boolean;
|
||||
isReply: boolean;
|
||||
}
|
||||
|
||||
|
||||
export const buildFilter = (tags: string[], npubs: string[], withReposts = false) => {
|
||||
const filter: NDKFilter = {
|
||||
|
@ -3,8 +3,9 @@ import ReactDOM from 'react-dom/client';
|
||||
import { NDKProvider } from '@nostr-dev-kit/ndk-react';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
import {defaultRelays} from './components/env'
|
||||
import { defaultRelays } from './components/env';
|
||||
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
||||
import GlobalState from './utils/globalState';
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -35,6 +36,8 @@ const router = createBrowserRouter([
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<NDKProvider relayUrls={defaultRelays}>
|
||||
<RouterProvider router={router} />
|
||||
<GlobalState>
|
||||
<RouterProvider router={router} />
|
||||
</GlobalState>
|
||||
</NDKProvider>
|
||||
);
|
||||
|
29
src/utils/globalState.tsx
Normal file
29
src/utils/globalState.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React, { createContext, useContext, useReducer } from 'react';
|
||||
|
||||
// Interface for our state
|
||||
interface GlobalState {
|
||||
userNPub?: string;
|
||||
}
|
||||
const initialState: GlobalState = {
|
||||
userNPub: undefined,
|
||||
};
|
||||
|
||||
type GlobalStateType = [GlobalState, React.Dispatch<Partial<GlobalState>>];
|
||||
|
||||
export const GlobalStateContext = createContext<GlobalStateType>([initialState, () => {}]);
|
||||
|
||||
const GlobalState = ({ children }: { children: React.ReactElement }) => {
|
||||
const [state, setState] = useReducer(
|
||||
(state: GlobalState, newState: Partial<GlobalState>) => ({
|
||||
...state,
|
||||
...newState,
|
||||
}),
|
||||
initialState
|
||||
);
|
||||
|
||||
return <GlobalStateContext.Provider value={[state, setState]}>{children}</GlobalStateContext.Provider>;
|
||||
};
|
||||
|
||||
export default GlobalState;
|
||||
|
||||
export const useGlobalState = () => useContext(GlobalStateContext);
|
1
src/webln-types.d.ts
vendored
Normal file
1
src/webln-types.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="@webbtc/webln-types" />
|
Loading…
Reference in New Issue
Block a user