feat: enable voting in other components

This commit is contained in:
MTG2000
2022-05-21 16:21:28 +03:00
parent d370c60403
commit 3709b4d93c
13 changed files with 172 additions and 116 deletions

View File

@@ -133,6 +133,7 @@ const voteMutation = extendType({
const { item_id, item_type, amount_in_sat } = args;
const lightning_address = (await getLightningAddress(item_id, item_type)) ?? BOLT_FUN_LIGHTNING_ADDRESS;
const pr = await getPaymetRequestForItem(lightning_address, args.amount_in_sat);
console.log(pr);
const invoice = parsePaymentRequest({ request: pr });
// #TODO remove votes rows that get added but not confirmed after some time

View File

@@ -19,8 +19,8 @@ interface Particle {
}
type Props = {
initVotes: number,
onVote?: (Vote: number) => void,
votes: number,
onVote?: (amount: number, config: Partial<{ onSetteled: () => void }>) => void,
fillType?: 'leftRight' | 'upDown' | "background" | 'radial',
direction?: 'horizontal' | 'vertical'
disableCounter?: boolean
@@ -30,7 +30,7 @@ type Props = {
} & Omit<ComponentProps<typeof Button>, 'children'>
export default function VoteButton({
initVotes,
votes,
onVote = () => { },
fillType = 'leftRight',
direction = 'horizontal',
@@ -56,9 +56,9 @@ export default function VoteButton({
const resetCounterOnRelease = resetCounterOnReleaseProp;
const doVote = useDebouncedCallback(() => {
onVote(voteCntRef.current);
const amount = voteCntRef.current;
onVote(amount, { onSetteled: () => setVoteCnt(v => v - amount) });
voteCntRef.current = 0;
console.log("VOTED");
}, [], 2000)
@@ -192,7 +192,7 @@ export default function VoteButton({
<MdLocalFireDepartment
className={`text-body2 ${incrementsCount ? "text-red-600" : "text-red-600"}`}
/><span className="align-middle w-[4ch]"> {numberFormatter(initVotes + voteCnt)}</span>
/><span className="align-middle w-[4ch]"> {numberFormatter(votes + voteCnt)}</span>
</div>
</div>
{increments.map(increment => <span

View File

@@ -61,7 +61,7 @@ export default function BountyCard({ bounty }: Props) {
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24 items-center">
<VoteButton initVotes={bounty.votes_count} dense />
<VoteButton votes={bounty.votes_count} dense />
<div className="text-gray-600">
<FiUsers /> <span className="align-middle text-body5">{bounty.applicants_count} Applicants</span>
</div>

View File

@@ -52,7 +52,7 @@ export default function QuestionCard({ question }: Props) {
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24 items-center">
<VoteButton initVotes={question.votes_count} dense />
<VoteButton votes={question.votes_count} dense />
<div className="text-gray-600">
<FiUsers /> <span className="align-middle text-body5">{question.answers_count} Answers</span>
</div>

View File

@@ -1,9 +1,10 @@
import VotesCount from "src/Components/VotesCount/VotesCount"
import { Story } from "src/features/Posts/types"
import Header from "../Header/Header"
import { BiComment } from 'react-icons/bi'
import { Link } from "react-router-dom"
import VoteButton from "src/Components/VoteButton/VoteButton"
import { useVote } from "src/utils/hooks"
import { Vote_Item_Type } from 'src/graphql';
export type StoryCardType = Pick<Story,
| 'id'
@@ -21,6 +22,12 @@ interface Props {
story: StoryCardType
}
export default function StoryCard({ story }: Props) {
const { vote } = useVote({
itemId: story.id,
itemType: Vote_Item_Type.Story
});
return (
<div className="bg-white rounded-12 overflow-hidden border">
<img src={story.cover_image} className='h-[200px] w-full object-cover' alt="" />
@@ -33,7 +40,7 @@ export default function StoryCard({ story }: Props) {
<hr className="my-16 bg-gray-200" />
<div className="flex gap-24 items-center">
<VoteButton initVotes={story.votes_count} dense />
<VoteButton votes={story.votes_count} dense onVote={vote} />
<div className="text-gray-600">
<BiComment /> <span className="align-middle text-body5">{story.comments_count} Comments</span>
</div>

View File

@@ -13,7 +13,7 @@ export default function TrendingCard() {
return (
<div className="bg-white rounded-8 border p-16">
<h3 className="text-body2 font-bolder mb-16">Trending on BOLT.FUN</h3>
<ul className='flex flex-col gap-16'>
<ul className='flex flex-col'>
{
trendingPosts.loading ?
Array(4).fill(0).map((_, idx) => <li key={idx} className="flex items-start gap-8">
@@ -24,7 +24,7 @@ export default function TrendingCard() {
)
:
trendingPosts.data?.getTrendingPosts.map(post => {
return <Link key={post.id} to={`/blog/post/${post.__typename}/${post.id}`} className="border-b pb-4 last-of-type:border-b-0">
return <Link key={post.id} to={`/blog/post/${post.__typename}/${post.id}`} className="border-b py-16 last-of-type:border-b-0">
<li className="flex items-start gap-8">
<Avatar width={24} src={post.author.avatar} />
<p className="text-body5 font-medium">{post.title}</p>

View File

@@ -43,10 +43,10 @@ export default function FeedPage() {
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
overflowY: "scroll",
}}>
<SortBy
{/* <SortBy
filterChanged={setSortByFilter}
/>
<div className="my-24"></div>
<div className="my-24"></div> */}
<PopularTopicsFilter
filterChanged={setTopicFilter}
/>

View File

@@ -52,7 +52,7 @@ export default function PopularTopicsFilter({ filterChanged }: Props) {
>
<span className='bg-gray-50 rounded-8 w-40 h-40 text-center py-8'> </span>
<span className="self-center px-16"><Skeleton width={'10ch'} />
<span className="self-center px-16"><Skeleton width={'7ch'} />
</span>
</li>
)

View File

@@ -1,33 +1,62 @@
import { BsBookmark } from "react-icons/bs"
import { FiArrowLeft } from "react-icons/fi"
import { MdIosShare } from "react-icons/md"
import { useNavigate } from "react-router-dom"
import VoteButton from "src/Components/VoteButton/VoteButton"
import { Post } from "src/features/Posts/types"
import { Vote_Item_Type } from "src/graphql"
import { useVote } from "src/utils/hooks"
interface Props {
votes_count: number
post: Pick<Post,
| 'id'
| 'votes_count'
| '__typename'
>
}
export default function PostActions(props: Props) {
export default function PostActions({ post }: Props) {
const actions = [
{
icon: BsBookmark,
value: 27
},
{
icon: MdIosShare,
value: 72
value: '--'
},
]
];
const navigate = useNavigate();
const { vote } = useVote({
itemId: post.id,
itemType: Vote_Item_Type[post.__typename!]
});
return (
<ul className="bg-white rounded-12 p-16 border flex justify-around md:flex-col gap-32">
<VoteButton initVotes={props.votes_count} direction='vertical' fillType="upDown" />
{actions.map((action, idx) => <li
className={`py-8 px-20 text-body5 flex flex-col justify-center items-center cursor-pointer rounded-8
<div>
<button className={`
hidden md:flex w-full aspect-square bg-white rounded-12 border justify-around items-center text-gray-500 hover:bg-gray-50 active:bg-gray-100
`}
onClick={() => navigate(-1)}
>
<FiArrowLeft className={"text-body1"} />
</button>
{/* <ul className="bg-white rounded-12 p-16 border flex justify-around md:flex-col gap-32">
<li
className={`py-8 px-20 text-body5 flex flex-col justify-center items-center cursor-pointer rounded-8
${'text-gray-500 hover:bg-gray-50 active:bg-gray-100'}`}>
<action.icon className={"text-body4 mb-8"}></action.icon>
<span>{action.value}</span>
</li>)}
</ul>
<FiArrowLeft className={"text-body4 mb-8"} />
</li>
</ul> */}
<ul className="bg-white rounded-12 p-16 border flex justify-around md:flex-col gap-32 mt-32">
{actions.map((action, idx) => <li
key={idx}
className={`py-8 px-20 text-body5 flex flex-col justify-center items-center cursor-pointer rounded-8
${'text-gray-500 hover:bg-gray-50 active:bg-gray-100'}`}>
<action.icon className={"text-body4 mb-8"}></action.icon>
<span>{action.value}</span>
</li>)}
<VoteButton votes={post.votes_count} onVote={vote} direction='vertical' fillType="upDown" />
</ul>
</div>
)
}

View File

@@ -19,7 +19,6 @@ export default function PostDetailsPage() {
type: type as any
},
skip: isNaN(Number(id)),
})
const { navHeight } = useAppSelector((state) => ({
@@ -44,7 +43,7 @@ export default function PostDetailsPage() {
top: `${navHeight + 16}px`,
maxHeight: `calc(100vh - ${navHeight}px - 16px)`,
}}>
<PostActions votes_count={post.votes_count} />
<PostActions post={post} />
</div>
</aside>

View File

@@ -28,16 +28,8 @@ export default function VoteCard({ onClose, direction, projectId, initVotes, ...
const [selectedOption, setSelectedOption] = useState(10);
const [voteAmount, setVoteAmount] = useState<number>(initVotes ?? 10);
const { vote, paymentStatus } = useVote({
onSuccess: () => {
setTimeout(() => {
onClose?.();
}, 4000);
},
onError: () => {
setTimeout(() => {
onClose?.();
}, 4000);
}
itemId: projectId,
itemType: Vote_Item_Type.Project
})
@@ -54,7 +46,18 @@ export default function VoteCard({ onClose, direction, projectId, initVotes, ...
const requestPayment = (e: FormEvent) => {
e.preventDefault();
vote({ variables: { "amountInSat": voteAmount, "itemId": projectId!, itemType: Vote_Item_Type.Project } });
vote(voteAmount, {
onSuccess: () => {
setTimeout(() => {
onClose?.();
}, 4000);
},
onError: () => {
setTimeout(() => {
onClose?.();
}, 4000);
}
});
}
return (

View File

@@ -7,7 +7,7 @@ $screen-xs-min: 320px;
body {
overflow-x: hidden;
/* background-color: #F8FAFC; */
background-color: #f8fafc;
}
.page-container {

View File

@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
import { useState } from 'react';
import { useConfirmVoteMutation, useVoteMutation } from 'src/graphql';
import { useCallback, useState } from 'react';
import { useConfirmVoteMutation, useVoteMutation, Vote_Item_Type } from 'src/graphql';
import { Wallet_Service } from 'src/services';
export enum PaymentStatus {
@@ -15,80 +15,97 @@ export enum PaymentStatus {
}
export const useVote = ({ onSuccess, onError }: {
onSuccess?: () => void
onError?: (error: any) => void
export const useVote = ({ itemId, itemType }: {
itemType: Vote_Item_Type,
itemId: number
}) => {
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.DEFAULT);
const [voteMutaion] = useVoteMutation();
const [confirmVote] = useConfirmVoteMutation();
const [voteMutaion] = useVoteMutation({
onCompleted: async (votingData) => {
try {
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
const webln = await Wallet_Service.getWebln()
const paymentResponse = await webln.sendPayment(votingData.vote.payment_request);
setPaymentStatus(PaymentStatus.PAID);
confirmVote({
variables: {
paymentRequest: votingData.vote.payment_request,
preimage: paymentResponse.preimage
}
})
} catch (error) {
console.log(error);
setPaymentStatus(PaymentStatus.NOT_PAID);
}
},
onError: (error) => {
console.log(error);
alert("Something wrong happened...")
setPaymentStatus(PaymentStatus.NOT_PAID);
onError?.(error)
}
});
const [confirmVote] = useConfirmVoteMutation({
onCompleted: () => {
setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED);
onSuccess?.();
},
update(cache, { data }) {
try {
const { item_id, item_type, amount_in_sat } = data!.confirmVote;
const { votes_count } = cache.readFragment({
id: `${item_type}:${item_id}`,
fragment: gql`
fragment My${item_type} on ${item_type} {
votes_count
}`
}) ?? {};
cache.writeFragment({
id: `${item_type}:${item_id}`,
fragment: gql`
fragment My${item_type} on ${item_type} {
votes_count
}
`,
data: {
votes_count: votes_count + amount_in_sat
},
})
} catch (error) {
}
},
onError: () => { }
});
const vote = (...params: Parameters<typeof voteMutaion>) => {
const vote = useCallback((amount: number, config?: Partial<{
onSuccess: () => void,
onError: (error: any) => void,
onSetteled: () => void
}>) => {
setPaymentStatus(PaymentStatus.FETCHING_PAYMENT_DETAILS)
voteMutaion(...params)
}
voteMutaion({
variables: {
itemId,
itemType,
amountInSat: amount
},
onCompleted: async (votingData) => {
try {
setPaymentStatus(PaymentStatus.AWAITING_PAYMENT);
const webln = await Wallet_Service.getWebln()
const paymentResponse = await webln.sendPayment(votingData.vote.payment_request);
setPaymentStatus(PaymentStatus.PAID);
//Confirm Voting payment
confirmVote({
variables: {
paymentRequest: votingData.vote.payment_request,
preimage: paymentResponse.preimage
},
onCompleted: () => {
setPaymentStatus(PaymentStatus.PAYMENT_CONFIRMED);
config?.onSuccess?.();
config?.onSetteled?.()
},
update(cache, { data }) {
try {
const { item_id, item_type, amount_in_sat } = data!.confirmVote;
const { votes_count } = cache.readFragment({
id: `${item_type}:${item_id}`,
fragment: gql`
fragment My${item_type} on ${item_type} {
votes_count
}`
}) ?? {};
cache.writeFragment({
id: `${item_type}:${item_id}`,
fragment: gql`
fragment My${item_type} on ${item_type} {
votes_count
}
`,
data: {
votes_count: votes_count + amount_in_sat
},
})
} catch (error) {
config?.onError?.(error)
}
},
onError: (error) => {
config?.onError?.(error);
config?.onSetteled?.();
}
})
} catch (error) {
setPaymentStatus(PaymentStatus.CANCELED);
config?.onError?.(error);
config?.onSetteled?.();
alert("Payment rejected by user")
}
},
onError: (error) => {
console.log(error);
alert("Something wrong happened...")
setPaymentStatus(PaymentStatus.NOT_PAID);
config?.onError?.(error);
config?.onSetteled?.();
}
})
}, [confirmVote, itemId, itemType, voteMutaion]);
return {
paymentStatus,
vote