mirror of
https://github.com/aljazceru/landscape-template.git
synced 2026-01-03 14:34:27 +01:00
feat: connect donate card with api & handle states
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
76
src/features/Donations/components/DonateCard/useDonate.tsx
Normal file
76
src/features/Donations/components/DonateCard/useDonate.tsx
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ export * from "./useReachedBottom";
|
||||
export * from "./useAutoResizableTextArea";
|
||||
export * from "./useCopyToClipboard";
|
||||
export * from "./useVote";
|
||||
export * from './useWindowSize'
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
33
src/utils/hooks/useWindowSize.ts
Normal file
33
src/utils/hooks/useWindowSize.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user