mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-02-23 15:34:21 +01:00
feat: rewrote the way wallet service work
moved the wallet setup logic to a dedicated service that remembers if user connected before and connectes automatically on refreshs
This commit is contained in:
28
src/App.tsx
28
src/App.tsx
@@ -3,27 +3,29 @@ import Navbar from "src/Components/Navbar/Navbar";
|
||||
import ExplorePage from "src/pages/ExplorePage";
|
||||
import ModalsContainer from "src/Components/Modals/ModalsContainer/ModalsContainer";
|
||||
import { useAppDispatch, useAppSelector, useResizeListener } from './utils/hooks';
|
||||
import { connectWallet } from 'src/redux/features/wallet.slice';
|
||||
import { setIsMobileScreen } from "src/redux/features/theme.slice";
|
||||
import { Wallet_Service } from "./services";
|
||||
|
||||
function App() {
|
||||
const { isWalletConnected, webln } = useAppSelector(state => ({
|
||||
const { isWalletConnected } = useAppSelector(state => ({
|
||||
isWalletConnected: state.wallet.isConnected,
|
||||
webln: state.wallet.provider,
|
||||
}));
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window.webln != "undefined") {
|
||||
window.webln.enable().then((res: any) => {
|
||||
dispatch(connectWallet(window.webln));
|
||||
console.log("called:webln.enable()", res);
|
||||
}).catch((err: any) => {
|
||||
console.log("error:webln.enable()", err);
|
||||
});
|
||||
}
|
||||
}, [dispatch]);
|
||||
// if (typeof window.webln != "undefined") {
|
||||
// alert('hi')
|
||||
// window.webln.enable().then((res: any) => {
|
||||
// dispatch(connectWallet(window.webln));
|
||||
// console.log("called:webln.enable()", res);
|
||||
// }).catch((err: any) => {
|
||||
// console.log("error:webln.enable()", err);
|
||||
// });
|
||||
// }
|
||||
setTimeout(() => {
|
||||
Wallet_Service.init();
|
||||
}, 2000)
|
||||
}, []);
|
||||
|
||||
useResizeListener(() => {
|
||||
// dispatch(setIsMobileScreen(/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)));
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function Badge(
|
||||
size = 'md',
|
||||
className,
|
||||
href,
|
||||
shadow = 'sm',
|
||||
shadow = 'none',
|
||||
children,
|
||||
isLoading,
|
||||
onRemove,
|
||||
@@ -50,7 +50,7 @@ export default function Badge(
|
||||
: PropsWithChildren<Props>) {
|
||||
|
||||
const classes = `
|
||||
rounded-48 shadow-${shadow} inline-block relative align-middle
|
||||
rounded-48 shadow-${shadow} border inline-block relative align-middle
|
||||
${badgrColor[color]}
|
||||
${badgeSize[size]}
|
||||
${className}
|
||||
|
||||
@@ -28,9 +28,8 @@ export default function Navbar() {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const { isWalletConnected, webln } = useAppSelector(state => ({
|
||||
const { isWalletConnected } = useAppSelector(state => ({
|
||||
isWalletConnected: state.wallet.isConnected,
|
||||
webln: state.wallet.provider,
|
||||
}));
|
||||
|
||||
const toggleSearch = () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Wrapper from './utils/Wrapper';
|
||||
import {
|
||||
ApolloClient,
|
||||
InMemoryCache,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import Wrapper from './utils/Wrapper';
|
||||
|
||||
const client = new ApolloClient({
|
||||
uri: 'https://xenodochial-goldstine-d09942.netlify.app/.netlify/functions/graphql',
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ALL_CATEGORIES_PROJECTS_QUERY, ALL_CATEGORIES_PROJECTS_RES } from "./qu
|
||||
export default function ProjectsSection() {
|
||||
|
||||
const { data, loading } = useQuery<ALL_CATEGORIES_PROJECTS_RES>(ALL_CATEGORIES_PROJECTS_QUERY);
|
||||
console.log(data, loading);
|
||||
|
||||
if (loading || !data) return <div className='mt-32 lg:mt-48'>
|
||||
{Array(3).fill(0).map((_, idx) => <ProjectsRowSkeleton key={idx} />)}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import { BsJoystick } from 'react-icons/bs'
|
||||
import { MdClose, MdLocalFireDepartment } from 'react-icons/md';
|
||||
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
||||
import { ModalCard } from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useAppDispatch, useAppSelector } from 'src/utils/hooks';
|
||||
import { openModal, scheduleModal } from 'src/redux/features/modals.slice';
|
||||
import { setProject } from 'src/redux/features/project.slice';
|
||||
import { connectWallet } from 'src/redux/features/wallet.slice';
|
||||
import Button from 'src/Components/Button/Button';
|
||||
import { requestProvider } from 'webln';
|
||||
import { PROJECT_BY_ID_QUERY, PROJECT_BY_ID_RES, PROJECT_BY_ID_VARS } from './query'
|
||||
import { AiFillThunderbolt } from 'react-icons/ai';
|
||||
import ProjectCardSkeleton from './ProjectCard.Skeleton'
|
||||
import TipButton from 'src/Components/TipButton/TipButton';
|
||||
import { Wallet_Service } from 'src/services'
|
||||
|
||||
|
||||
interface Props extends ModalCard {
|
||||
@@ -33,9 +31,8 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
|
||||
}
|
||||
);
|
||||
|
||||
const { isWalletConnected, webln, project, isMobileScreen } = useAppSelector(state => ({
|
||||
const { isWalletConnected, project, isMobileScreen } = useAppSelector(state => ({
|
||||
isWalletConnected: state.wallet.isConnected,
|
||||
webln: state.wallet.provider,
|
||||
project: state.project.project,
|
||||
isMobileScreen: state.theme.isMobileScreen
|
||||
}));
|
||||
@@ -46,24 +43,11 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
|
||||
return <ProjectCardSkeleton onClose={onClose} direction={direction} isPageModal={props.isPageModal} />;
|
||||
|
||||
const onConnectWallet = async () => {
|
||||
try {
|
||||
|
||||
const webln = await requestProvider();
|
||||
if (webln) {
|
||||
dispatch(connectWallet(webln));
|
||||
alert("wallet connected!");
|
||||
}
|
||||
// Now you can call all of the webln.* methods
|
||||
}
|
||||
catch (err: any) {
|
||||
// Tell the user what went wrong
|
||||
alert(err.message);
|
||||
}
|
||||
Wallet_Service.connectWallet()
|
||||
}
|
||||
|
||||
const onTip = (tip?: number) => {
|
||||
|
||||
|
||||
if (!isWalletConnected) {
|
||||
dispatch(scheduleModal({ Modal: 'TipCard', props: { tipValue: tip } }))
|
||||
dispatch(openModal({
|
||||
@@ -116,7 +100,7 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
|
||||
{isWalletConnected ?
|
||||
<TipButton onTip={onTip} />
|
||||
:
|
||||
<Button onClick={onConnectWallet} size='md' className="border border-gray-200 bg-gray-100 hover:bg-gray-50 active:bg-gray-100 my-16">Connect Wallet to Vote</Button>
|
||||
<Button onClick={onConnectWallet} size='md' className="border border-gray-200 bg-gray-100 hover:bg-gray-50 active:bg-gray-100 my-16">Connect Wallet to Tip</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,7 +110,7 @@ export default function ProjectCard({ onClose, direction, projectId, ...props }:
|
||||
{isWalletConnected ?
|
||||
<TipButton fullWidth onTip={onTip} />
|
||||
:
|
||||
<Button size='md' fullWidth className="bg-gray-200 hover:bg-gray-100 mb-24" onClick={onConnectWallet}><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet to Vote</Button>
|
||||
<Button size='md' fullWidth className="bg-gray-200 hover:bg-gray-100 mb-24" onClick={onConnectWallet}><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet to Tip</Button>
|
||||
}
|
||||
</div>
|
||||
<div className="mt-40">
|
||||
|
||||
@@ -3,10 +3,11 @@ import React, { useState } from 'react';
|
||||
import { AiFillThunderbolt } from 'react-icons/ai'
|
||||
import { IoClose } from 'react-icons/io5'
|
||||
import { ModalCard, modalCardVariants } from 'src/Components/Modals/ModalsContainer/ModalsContainer';
|
||||
import { useAppDispatch, useAppSelector } from 'src/utils/hooks';
|
||||
import { gql, useQuery, useMutation } from "@apollo/client";
|
||||
import { useAppSelector } from 'src/utils/hooks';
|
||||
import { gql, useMutation } from "@apollo/client";
|
||||
import useWindowSize from "react-use/lib/useWindowSize";
|
||||
import Confetti from "react-confetti";
|
||||
import { Wallet_Service } from 'src/services';
|
||||
|
||||
const defaultOptions = [
|
||||
{ text: '10 sat', value: 10 },
|
||||
@@ -14,6 +15,7 @@ const defaultOptions = [
|
||||
{ text: '1k sats', value: 1000 },
|
||||
]
|
||||
|
||||
|
||||
enum PaymentStatus {
|
||||
DEFAULT,
|
||||
FETCHING_PAYMENT_DETAILS,
|
||||
@@ -55,20 +57,19 @@ interface Props extends ModalCard {
|
||||
export default function TipCard({ onClose, direction, tipValue, ...props }: Props) {
|
||||
const { width, height } = useWindowSize()
|
||||
|
||||
const { isWalletConnected, webln } = useAppSelector(state => ({
|
||||
const { isWalletConnected } = useAppSelector(state => ({
|
||||
isWalletConnected: state.wallet.isConnected,
|
||||
webln: state.wallet.provider,
|
||||
}));
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState(10);
|
||||
const [voteAmount, setVoteAmount] = useState<number>(tipValue ?? 10);
|
||||
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.DEFAULT);
|
||||
|
||||
const [vote, { data }] = useMutation(VOTE, {
|
||||
onCompleted: (votingData) => {
|
||||
onCompleted: async (votingData) => {
|
||||
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
|
||||
const webln = await Wallet_Service.getWebln()
|
||||
webln.sendPayment(votingData.vote.payment_request).then((res: any) => {
|
||||
console.log("waiting for payment", res);
|
||||
confirmVote({ variables: { paymentRequest: votingData.vote.payment_request, preimage: res.preimage } });
|
||||
@@ -84,6 +85,9 @@ export default function TipCard({ onClose, direction, tipValue, ...props }: Prop
|
||||
const [confirmVote, { data: confirmedVoteData }] = useMutation(CONFIRM_VOTE, {
|
||||
onCompleted: (votingData) => {
|
||||
setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED);
|
||||
setTimeout(() => {
|
||||
onClose?.();
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,7 +116,7 @@ export default function TipCard({ onClose, direction, tipValue, ...props }: Prop
|
||||
className="modal-card max-w-[343px] p-24 rounded-xl relative"
|
||||
>
|
||||
<IoClose className='absolute text-body2 top-24 right-24 hover:cursor-pointer' onClick={onClose} />
|
||||
<h2 className='text-h5 font-bold'>Upvote Project</h2>
|
||||
<h2 className='text-h5 font-bold'>Tip this Project</h2>
|
||||
<div className="mt-32 ">
|
||||
<label className="block text-gray-700 text-body4 mb-2 ">
|
||||
Enter Amount
|
||||
@@ -143,7 +147,7 @@ export default function TipCard({ onClose, direction, tipValue, ...props }: Prop
|
||||
{paymentStatus === PaymentStatus.AWAITING_PAYMENT && <p className="text-body6 mt-12 text-yellow-500">Please confirm the payment in the prompt...</p>}
|
||||
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <p className="text-body6 mt-12 text-green-500">Imagine confetti here</p>}
|
||||
<button className="btn btn-primary w-full mt-32" onClick={requestPayment}>
|
||||
Upvote
|
||||
Tip
|
||||
</button>
|
||||
</div>
|
||||
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <Confetti width={width} height={height} />}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { requestProvider } from "webln";
|
||||
import { WebLNProvider } from "webln";
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface StoreState {
|
||||
isConnected: boolean;
|
||||
isLoading: boolean;
|
||||
provider: any;
|
||||
|
||||
|
||||
type StoreState = {
|
||||
isConnected: boolean,
|
||||
}
|
||||
|
||||
|
||||
const isWebLNConnected = () => {
|
||||
// since webln spec expects webln.enable() to be called on each load
|
||||
// and extensions like alby do not inject the webln object with true
|
||||
@@ -23,7 +24,6 @@ const isWebLNConnected = () => {
|
||||
|
||||
const initialState = {
|
||||
isConnected: false,
|
||||
isLoading: false,
|
||||
provider: null,
|
||||
} as StoreState;
|
||||
|
||||
@@ -31,9 +31,8 @@ export const walletSlice = createSlice({
|
||||
name: "wallet",
|
||||
initialState,
|
||||
reducers: {
|
||||
connectWallet(state, action: PayloadAction<any>) {
|
||||
state.isConnected = action.payload ? true : false;
|
||||
state.provider = action.payload;
|
||||
connectWallet(state) {
|
||||
state.isConnected = true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
5
src/services/index.ts
Normal file
5
src/services/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import Wallet_Service from './wallet.service'
|
||||
|
||||
export {
|
||||
Wallet_Service
|
||||
}
|
||||
83
src/services/wallet.service.ts
Normal file
83
src/services/wallet.service.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { requestProvider, MissingProviderError, WebLNProvider } from 'webln';
|
||||
import { store } from '../redux/store'
|
||||
import { connectWallet as connectWalletStore } from '../redux/features/wallet.slice'
|
||||
|
||||
|
||||
class _Wallet_Service {
|
||||
|
||||
isConnected = false;
|
||||
webln: WebLNProvider | null = null;
|
||||
|
||||
async getWebln() {
|
||||
if (!this.isConnected) await this.connectWallet();
|
||||
return this.webln as WebLNProvider;
|
||||
}
|
||||
|
||||
init() {
|
||||
const connectedPreviously = localStorage.getItem('wallet-connected')
|
||||
if (connectedPreviously)
|
||||
this.connectWallet();
|
||||
}
|
||||
|
||||
async connectWallet() {
|
||||
try {
|
||||
const webln = await requestProvider();
|
||||
store.dispatch(connectWalletStore())
|
||||
localStorage.setItem('wallet-connected', 'yes')
|
||||
this.webln = webln;
|
||||
this.isConnected = false;
|
||||
}
|
||||
catch (err: any) {
|
||||
// Default error message
|
||||
let message = `Couldn't connect wallet`;
|
||||
// If they didn't have a provider, point them to Joule
|
||||
if (err.constructor === MissingProviderError) {
|
||||
message = "Check out https://lightningjoule.com to get a WebLN provider";
|
||||
}
|
||||
|
||||
|
||||
|
||||
localStorage.removeItem('wallet-connected')
|
||||
// Show the error (though you should probably use something better than alert!)
|
||||
alert(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Wallet_Service = new _Wallet_Service()
|
||||
export default Wallet_Service;
|
||||
|
||||
// export async function initWallet() {
|
||||
// const connectedPreviously = localStorage.getItem('wallet-connected')
|
||||
// if (connectedPreviously)
|
||||
// connectWallet();
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// export async function connectWallet() {
|
||||
// try {
|
||||
// const webln = await requestProvider();
|
||||
// store.dispatch(connectWalletStore(webln))
|
||||
// localStorage.setItem('wallet-connected', 'yes')
|
||||
// }
|
||||
// catch (err: any) {
|
||||
// // Default error message
|
||||
// let message = `Couldn't connect wallet`;
|
||||
// // If they didn't have a provider, point them to Joule
|
||||
// if (err.constructor === MissingProviderError) {
|
||||
// message = "Check out https://lightningjoule.com to get a WebLN provider";
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// localStorage.removeItem('wallet-connected')
|
||||
// // Show the error (though you should probably use something better than alert!)
|
||||
// alert(message);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { store } from '../redux/store';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { store } from '../redux/store';
|
||||
|
||||
import 'react-multi-carousel/lib/styles.css';
|
||||
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
|
||||
|
||||
Reference in New Issue
Block a user