feat: connect donate card with api & handle states

This commit is contained in:
MTG2000
2022-05-25 11:23:11 +03:00
parent 587c4c96a2
commit f1f0be7f79
9 changed files with 137 additions and 23 deletions

View File

@@ -14,6 +14,7 @@ const HottestPage = React.lazy(() => import("src/features/Projects/pages/Hottest
const PostDetailsPage = React.lazy(() => import("./features/Posts/pages/PostDetailsPage/PostDetailsPage"))
const CategoryPage = React.lazy(() => import("src/features/Projects/pages/CategoryPage/CategoryPage"))
const ExplorePage = React.lazy(() => import("src/features/Projects/pages/ExplorePage"))
const DonatePage = React.lazy(() => import("./features/Donations/pages/DonatePage/DonatePage"))
function App() {
const { isWalletConnected } = useAppSelector(state => ({
@@ -49,6 +50,7 @@ function App() {
<Route path="/blog/post/:type/:id" element={<PostDetailsPage />} />
<Route path="/blog" element={<FeedPage />} />
<Route path="/hackathons" element={<HackathonsPage />} />
<Route path="/donate" element={<DonatePage />} />
<Route path="/" element={<ExplorePage />} />
</Routes>
</Suspense>

View File

@@ -1,8 +1,8 @@
import React, { FormEvent, useState } from 'react';
import { PaymentStatus, useVote } from 'src/utils/hooks';
import { PaymentStatus, } from 'src/utils/hooks';
import Confetti from "react-confetti";
import { useWindowSize } from '@react-hookz/web';
import { Vote_Item_Type } from 'src/graphql';
import { useDonate } from './useDonate';
const defaultOptions = [
{ text: '500', value: 500 },
@@ -12,18 +12,13 @@ const defaultOptions = [
]
export default function DonateCard() {
const { width, height } = useWindowSize()
const size = useWindowSize();
const [selectedOption, setSelectedOption] = useState(-1);
const [donationAmount, setDonationAmount] = useState<number>();
const { vote, paymentStatus } = useVote({
itemId: 123,
itemType: Vote_Item_Type.Project
})
const { donate, paymentStatus, isLoading } = useDonate()
const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedOption(-1);
@@ -38,7 +33,7 @@ export default function DonateCard() {
const requestPayment = (e: FormEvent) => {
e.preventDefault();
if (donationAmount)
vote(donationAmount, {
donate(donationAmount, {
onSuccess: () => {
setTimeout(() => {
setDonationAmount(undefined);
@@ -86,18 +81,20 @@ export default function DonateCard() {
</div>
{paymentStatus === PaymentStatus.FETCHING_PAYMENT_DETAILS && <p className="text-body6 mt-12 text-yellow-500">Please wait while we the fetch payment details.</p>}
{paymentStatus === PaymentStatus.NOT_PAID && <p className="text-body6 mt-12 text-red-500">You did not confirm the payment. Please try again.</p>}
{paymentStatus === PaymentStatus.NETWORK_ERROR && <p className="text-body6 mt-12 text-red-500">A network error happened while fetching data.</p>}
{paymentStatus === PaymentStatus.PAID && <p className="text-body6 mt-12 text-green-500">The invoice was paid! Please wait while we confirm it.</p>}
{paymentStatus === PaymentStatus.AWAITING_PAYMENT && <p className="text-body6 mt-12 text-yellow-500">Waiting for your payment...</p>}
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <p className="text-body6 mt-12 text-green-500">Thanks for your vote</p>}
<button
type='submit'
className="btn btn-primary w-full mt-32"
disabled={paymentStatus !== PaymentStatus.DEFAULT && paymentStatus !== PaymentStatus.NOT_PAID}
disabled={isLoading}
>
{paymentStatus === PaymentStatus.DEFAULT || paymentStatus === PaymentStatus.NOT_PAID ? "Make a donation" : "Donating..."}
{!isLoading ? "Make a donation" : "Donating..."}
</button>
</form>
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <Confetti width={width} height={height} />}
{paymentStatus === PaymentStatus.PAYMENT_CONFIRMED && <Confetti className='!fixed top-0 left-0' recycle={false} width={size.width} height={size.height} />}
</div>
)
}

View File

@@ -0,0 +1,76 @@
import { useCallback, useState } from 'react';
import { useConfirmDonationMutation, useDonateMutation } from 'src/graphql';
import { Wallet_Service } from 'src/services';
import { PaymentStatus } from 'src/utils/hooks';
export const useDonate = () => {
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.DEFAULT);
const [donateMutation] = useDonateMutation();
const [confirmDonation] = useConfirmDonationMutation();
const donate = useCallback((amount: number, config?: Partial<{
onSuccess: () => void,
onError: (error: any) => void,
onSetteled: () => void
}>) => {
setPaymentStatus(PaymentStatus.FETCHING_PAYMENT_DETAILS)
donateMutation({
variables: {
amountInSat: amount
},
onCompleted: async (donationData) => {
try {
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
const webln = await Wallet_Service.getWebln()
const paymentResponse = await webln.sendPayment(donationData.donate.payment_request);
setPaymentStatus(PaymentStatus.PAID);
//Confirm Voting payment
confirmDonation({
variables: {
paymentRequest: donationData.donate.payment_request,
preimage: paymentResponse.preimage
},
onCompleted: () => {
setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED);
config?.onSuccess?.();
config?.onSetteled?.()
},
onError: (error) => {
console.log(error)
setPaymentStatus(PaymentStatus.NETWORK_ERROR);
config?.onError?.(error);
config?.onSetteled?.();
alert("A network error happened while confirming the payment...")
}
})
} catch (error) {
setPaymentStatus(PaymentStatus.CANCELED);
config?.onError?.(error);
config?.onSetteled?.();
alert("Payment rejected by user")
}
},
onError: (error) => {
console.log(error);
setPaymentStatus(PaymentStatus.NETWORK_ERROR);
config?.onError?.(error);
config?.onSetteled?.();
alert("A network error happened...")
}
})
}, [confirmDonation, donateMutation]);
const isLoading = paymentStatus !== PaymentStatus.DEFAULT && paymentStatus !== PaymentStatus.PAYMENT_CONFIRMED && paymentStatus !== PaymentStatus.NOT_PAID && paymentStatus !== PaymentStatus.NETWORK_ERROR
return {
paymentStatus,
donate,
isLoading
}
}

View File

@@ -20,7 +20,7 @@ export default function DonationStats() {
<StatCard
color="#22C55E"
label={<><IoMedalOutline className='scale-125 mr-8' /> <span className="align-middle">Prizes</span></>}
value='2.5k'
value='$2.5k'
/>
<StatCard
color="#3B82F6"

View File

@@ -1,3 +1,4 @@
import { BiCoinStack } from 'react-icons/bi'
import DonateCard from 'src/features/Donations/components/DonateCard/DonateCard'
import DonationStats from '../DonationStats/DonationStats'
import styles from './styles.module.scss'
@@ -9,7 +10,7 @@ export default function Header() {
<div className="flex items-center gap-24 flex-col md:flex-row">
<div>
<h1 className="text-[54px] font-bolder">
Donate
Donate <BiCoinStack className='ml-8' />
</h1>
<p className='text-h3 font-bolder mt-24'>
Help fund <span className="text-primary-600">BOLT🔩FUN</span>, as well as other <span className="text-primary-600">Makers</span> working on lightning apps through tournaments and prize pools

View File

@@ -2,6 +2,7 @@
.header {
padding: 56px 0;
min-height: calc(min(1080px, 90vh));
background: #ffecf9;
background: linear-gradient(40deg, white -5%, #ffb7d963 74%, #e3faff61 100%);
@@ -12,8 +13,4 @@
& > div {
grid-area: content;
}
@include gt-md {
padding: 156px 0;
}
}

View File

@@ -6,3 +6,4 @@ export * from "./useReachedBottom";
export * from "./useAutoResizableTextArea";
export * from "./useCopyToClipboard";
export * from "./useVote";
export * from './useWindowSize'

View File

@@ -11,10 +11,13 @@ export enum PaymentStatus {
AWAITING_PAYMENT,
PAYMENT_CONFIRMED,
NOT_PAID,
CANCELED
CANCELED,
NETWORK_ERROR
}
export const useVote = ({ itemId, itemType }: {
itemType: Vote_Item_Type,
itemId: number
@@ -81,8 +84,10 @@ export const useVote = ({ itemId, itemType }: {
},
onError: (error) => {
setPaymentStatus(PaymentStatus.NETWORK_ERROR);
config?.onError?.(error);
config?.onSetteled?.();
alert("A network error happened while confirming the payment...")
}
})
} catch (error) {
@@ -96,18 +101,20 @@ export const useVote = ({ itemId, itemType }: {
},
onError: (error) => {
console.log(error);
alert("Something wrong happened...")
setPaymentStatus(PaymentStatus.NOT_PAID);
setPaymentStatus(PaymentStatus.NETWORK_ERROR);
config?.onError?.(error);
config?.onSetteled?.();
alert("A network error happened...")
}
})
}, [confirmVote, itemId, itemType, voteMutaion]);
const isLoading = paymentStatus !== PaymentStatus.DEFAULT && paymentStatus !== PaymentStatus.PAYMENT_CONFIRMED && paymentStatus !== PaymentStatus.NOT_PAID && paymentStatus !== PaymentStatus.NETWORK_ERROR
return {
paymentStatus,
vote
vote,
isLoading,
}
}

View File

@@ -0,0 +1,33 @@
import { useState, useEffect } from "react";
// Define general type for useWindowSize hook, which includes width and height
interface Size {
width: number | undefined;
height: number | undefined;
}
// Hook
export function useWindowSize(): Size {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState<Size>({
width: undefined,
height: undefined,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}